-- -------------------------------------------------------------------------
-- Features/Heist.lua
-- Heist management interface for GTA Online
-- -------------------------------------------------------------------------

local module = {}

-- [Dependencies]
local ui = nil
local natives = nil
local notification = nil
local features_config = nil
local debugger = nil
local HeistHelper = nil
local heist_data = nil

-- [Natives Cache]
local N = {}

-- [Lua 5.4] <const> for function references
local pcall <const> = pcall
local type <const> = type
local ipairs <const> = ipairs
local pairs <const> = pairs
local tostring <const> = tostring
local math_floor <const> = math.floor

-- [Lua 5.4] <const> for module name
local MODULE_NAME <const> = "Heist"

local function _T(text)
    if _G and _G._T then
        return _G._T(text)
    end
    return text
end

-- Helper function to wrap option names with translation
local function translate_options(names)
    if not names then return {} end
    local translated = {}
    for i, name in ipairs(names) do
        translated[i] = _T(name)
    end
    return translated
end

-- -------------------------------------------------------------------------
-- [Interior Coordinates]
-- -------------------------------------------------------------------------

local TELEPORT_COORDS = {
    AGENCY = {x = -578.981, y = -711.381, z = 116.805},
    AUTOSHOP = {x = -1349.024, y = 138.381, z = -95.121},
    KOSATKA = {x = 1561.087, y = 386.610, z = -49.685},
    ARCADE = {x = 2712.995, y = -371.115, z = -54.780},
    FACILITY = {x = 483.2006, y = 4810.5405, z = -58.9191},
    APARTMENT = {x = -773.407, y = 312.105, z = 85.698},
    SALVAGE = {x = 1074.720, y = -2275.502, z = -48.999},
    MAZEBANK = {x = -75.146, y = -818.687, z = 326.175}
}

-- -------------------------------------------------------------------------
-- [Blip Sprite IDs]
-- -------------------------------------------------------------------------

local BLIP_SPRITES = {
    AGENCY = 826,
    AUTOSHOP = 779,
    KOSATKA = 760,
    ARCADE = 740,
    FACILITY = 590,
    APARTMENT = 40,
    SALVAGE = 867,
    HEIST = 428
}

-- -------------------------------------------------------------------------
-- [State]
-- -------------------------------------------------------------------------

local state = {
    -- Main subtab selection
    subtab_idx = 1,

    -- Mission Assist state
    assist = {
        skip_hacks = false,
        auto_drill = false,
        infinite_bag = false,
    },

    -- Solo Launch state
    solo_launch = {
        apartment = false,
        casino = false,
        doomsday = false,
    },

    -- Previous state for change detection
    solo_launch_prev = {
        apartment = false,
        casino = false,
        doomsday = false,
    },

    -- Agency state
    agency = {
        contract_idx = 1,
        payout = 1000000,
    },

    -- Cayo Perico state
    cayo = {
        difficulty_idx = 1,
        approach_idx = 1,
        loadout_idx = 1,
        target_idx = 1,
        compound_loot_idx = 1,
        compound_amount_idx = 1,
        island_loot_idx = 1,
        island_amount_idx = 1,
        arts_amount_idx = 1,
        -- Values
        cash_value = 83250,
        weed_value = 135000,
        coke_value = 202500,
        gold_value = 333333,
        arts_value = 180000,
        -- Cuts
        cut_preset_idx = 1,
        preset_cut = "100",
        player1_cut = 100,
        player2_cut = 100,
        player3_cut = 100,
        player4_cut = 100,
    },

    -- Diamond Casino state
    casino = {
        difficulty_idx = 1,
        approach_idx = 1,
        gunman_idx = 1,
        driver_idx = 1,
        hacker_idx = 1,
        masks_idx = 1,
        keycards_idx = 1,
        guards_idx = 1,
        target_idx = 1,
        -- Cuts
        cut_preset_idx = 1,
        preset_cut = "100",
        player1_cut = 100,
        player2_cut = 100,
        player3_cut = 100,
        player4_cut = 100,
    },

    -- Doomsday state
    doomsday = {
        act_idx = 1,
        cut_preset_idx = 1,
        preset_cut = "100",
        player1_cut = 100,
        player2_cut = 100,
        player3_cut = 100,
        player4_cut = 100,
    },

    -- Auto Shop state
    autoshop = {
        contract_idx = 1,
        payout = 1000000,
    },

    -- Salvage Yard state
    salvage = {
        slot1_robbery_idx = 1,
        slot1_vehicle_idx = 1,
        slot2_robbery_idx = 1,
        slot2_vehicle_idx = 1,
        slot3_robbery_idx = 1,
        slot3_vehicle_idx = 1,
    },

    -- Apartment state
    apartment = {
        preset_cut = "100",
        player1_cut = 25,
        player2_cut = 25,
        player3_cut = 25,
        player4_cut = 25,
    },

    -- Generic state
    generic = {
        cut_value = 100,
    },
}

-- -------------------------------------------------------------------------
-- [Helper Functions]
-- -------------------------------------------------------------------------

local function show_notify(msg, type_str)
    if notification and notification.notify then
        notification.notify(msg, type_str or "Info")
    end
end

local function log_info(msg)
    if debugger and debugger.info then
        debugger.info("[Heist] " .. tostring(msg))
    end
end

local function log_error(msg)
    if debugger and debugger.error then
        debugger.error("[Heist] " .. tostring(msg))
    end
end

-- -------------------------------------------------------------------------
-- [Teleport Functions]
-- -------------------------------------------------------------------------

local function get_blip_coords(blip_sprite)
    if not N.GET_FIRST_BLIP_INFO_ID then
        return nil
    end

    local blip = N.GET_FIRST_BLIP_INFO_ID(blip_sprite)
    if not blip or blip == 0 then
        return nil
    end

    while blip and blip ~= 0 do
        if N.DOES_BLIP_EXIST and N.DOES_BLIP_EXIST(blip) then
            local color = N.GET_BLIP_COLOUR and N.GET_BLIP_COLOUR(blip) or -1
            if color ~= 3 then
                if N.GET_BLIP_COORDS then
                    local coords = N.GET_BLIP_COORDS(blip)
                    if coords then
                        return {x = coords.x, y = coords.y, z = coords.z + 1.0}
                    end
                end
            end
        end
        if N.GET_NEXT_BLIP_INFO_ID then
            blip = N.GET_NEXT_BLIP_INFO_ID(blip_sprite)
        else
            break
        end
    end

    return nil
end

-- Check if Kosatka blip exists (directly check blip, not Status global)
local function is_kosatka_blip_exists()
    if not N.GET_FIRST_BLIP_INFO_ID then return false end

    local blip = N.GET_FIRST_BLIP_INFO_ID(BLIP_SPRITES.KOSATKA)
    return blip ~= nil and blip ~= 0
end

-- Request Kosatka to spawn in ocean if not present
local function request_kosatka()
    if not HeistHelper or not heist_data then return false end

    local kosatka = heist_data.GLOBALS.KOSATKA
    if not kosatka or not kosatka.REQUEST then return false end

    -- Set request flag
    HeistHelper.set_global(kosatka.REQUEST, 1)
    return true
end

local function teleport_to_entrance(blip_sprite, location_name)
    local me = players.me()
    if not me then
        show_notify(_T("Player not found"), "Error")
        return false
    end

    util.create_job(function()
        local ped = me.ped
        local veh = me.vehicle
        local entity = (veh and veh ~= 0) and veh or ped

        if N.FREEZE_ENTITY_POSITION then
            N.FREEZE_ENTITY_POSITION(entity, true)
        end

        -- Smart relay: Transit via outdoor safe point if in interior
        if me.in_interior then
            local transit_point = TELEPORT_COORDS.MAZEBANK
            if N.SET_ENTITY_COORDS_NO_OFFSET then
                N.SET_ENTITY_COORDS_NO_OFFSET(
                    entity,
                    transit_point.x,
                    transit_point.y,
                    transit_point.z,
                    false,
                    false,
                    false
                )
            end
            util.yield(800)
        end

        -- Kosatka special handling: Check blip and request if needed
        if blip_sprite == BLIP_SPRITES.KOSATKA then
            if not is_kosatka_blip_exists() then
                show_notify(_T("Kosatka not spawned, requesting..."), "Info")

                -- Request Kosatka and wait for blip to appear
                while not is_kosatka_blip_exists() do
                    request_kosatka()
                    util.yield()
                end

                show_notify(_T("Kosatka spawned successfully"), "Success")
            end
        end

        local coords = get_blip_coords(blip_sprite)
        if coords then
            if N.SET_ENTITY_COORDS_NO_OFFSET then
                N.SET_ENTITY_COORDS_NO_OFFSET(entity, coords.x, coords.y, coords.z, false, false, false)
            end

            -- For Kosatka: Wait for heist blip to appear after teleport
            if blip_sprite == BLIP_SPRITES.KOSATKA then
                -- Wait for heist blip (indicates Kosatka is ready)
                local blip_check = 0
                while blip_check == 0 and N.GET_CLOSEST_BLIP_INFO_ID do
                    blip_check = N.GET_CLOSEST_BLIP_INFO_ID(BLIP_SPRITES.HEIST)
                    if blip_check == 0 then
                        util.yield()  -- Yield per frame
                    end
                end
            end

            util.yield(500)
            show_notify(_T("Teleported to ") .. _T(location_name) .. _T(" entrance"), "Success")
        else
            show_notify(_T("Entrance blip not found"), "Warning")
        end

        if N.FREEZE_ENTITY_POSITION then
            N.FREEZE_ENTITY_POSITION(entity, false)
        end
    end)

    return true
end

local function teleport_to_interior(coords, location_name, heading)
    if not coords then
        show_notify(_T("Invalid coordinates"), "Error")
        return false
    end

    local me = players.me()
    if not me then
        show_notify(_T("Player not found"), "Error")
        return false
    end

    util.create_job(function()
        local ped = me.ped
        local veh = me.vehicle
        local entity = (veh and veh ~= 0) and veh or ped

        if N.FREEZE_ENTITY_POSITION then
            N.FREEZE_ENTITY_POSITION(entity, true)
        end

        if N.SET_ENTITY_COORDS_NO_OFFSET then
            N.SET_ENTITY_COORDS_NO_OFFSET(entity, coords.x, coords.y, coords.z, false, false, false)
        end

        if heading and N.SET_ENTITY_HEADING then
            N.SET_ENTITY_HEADING(entity, heading)
        end

        util.yield(500)

        if N.FREEZE_ENTITY_POSITION then
            N.FREEZE_ENTITY_POSITION(entity, false)
        end

        show_notify(_T("Teleported to ") .. _T(location_name), "Success")
    end)

    return true
end

local function teleport_to_heist_blip(heading, location_name)
    local me = players.me()
    if not me then
        show_notify(_T("Player not found"), "Error")
        return false
    end

    util.create_job(function()
        local ped = me.ped
        local veh = me.vehicle
        local entity = (veh and veh ~= 0) and veh or ped

        if N.FREEZE_ENTITY_POSITION then
            N.FREEZE_ENTITY_POSITION(entity, true)
        end

        local coords = get_blip_coords(BLIP_SPRITES.HEIST)
        if coords then
            if N.SET_ENTITY_COORDS_NO_OFFSET then
                N.SET_ENTITY_COORDS_NO_OFFSET(entity, coords.x, coords.y, coords.z, false, false, false)
            end

            if heading and N.SET_ENTITY_HEADING then
                N.SET_ENTITY_HEADING(entity, heading)
            end

            util.yield(500)
            show_notify(_T("Teleported to ") .. _T(location_name), "Success")
        else
            show_notify(_T("Heist board blip not found (enter property first)"), "Warning")
        end

        if N.FREEZE_ENTITY_POSITION then
            N.FREEZE_ENTITY_POSITION(entity, false)
        end
    end)

    return true
end

-- -------------------------------------------------------------------------
-- [Agency Functions]
-- -------------------------------------------------------------------------

local function agency_complete_preps(contract)
    if not HeistHelper then return false end

    local stats = heist_data.STATS.AGENCY

    HeistHelper.set_stat(stats.STORY_BS, contract)

    -- Set story strand based on contract progress
    local strand_value = -1
    if contract < 18 then
        strand_value = 0
    elseif contract < 128 then
        strand_value = 1
    elseif contract < 2044 then
        strand_value = 2
    end

    HeistHelper.set_stat(stats.STORY_STRAND, strand_value)
    HeistHelper.set_stat(stats.GENERAL_BS, -1)     -- -1 unlocks all
    HeistHelper.set_stat(stats.COMPLETED_BS, -1)   -- -1 completes all

    log_info("Agency preps completed")
    show_notify(_T("Agency preps completed"), "Success")
    return true
end

local function agency_set_payout(payout)
    if not HeistHelper then return false end

    local global = heist_data.GLOBALS.AGENCY_PAYOUT
    HeistHelper.set_global(global, payout)

    log_info("Agency payout set to: " .. payout)
    show_notify(_T("Agency payout set to: ") .. "$" .. payout, "Success")
    return true
end

local function agency_reset_cooldown()
    if not HeistHelper then return false end

    local stats = heist_data.STATS.AGENCY
    HeistHelper.set_stat(stats.STORY_COOLDOWN, -1)
    HeistHelper.set_stat(stats.SECURITY_COOLDOWN, -1)
    HeistHelper.set_stat(stats.PAYPHONE_COOLDOWN, 0)

    -- Reset global cooldowns
    local globals = heist_data.GLOBALS.PAYPHONE
    HeistHelper.set_global(globals.COOLDOWN, 0)
    HeistHelper.set_global(globals.SECURITY_COOLDOWN, 0)

    show_notify(_T("Agency cooldowns reset"), "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Cayo Perico Functions]
-- -------------------------------------------------------------------------

local function cayo_complete_preps(difficulty, approach, loadout, target, compound_loot, compound_amount, island_loot, island_amount)
    if not HeistHelper then return false end

    local stats = heist_data.STATS.CAYO
    local prep_vals = heist_data.VALUES.CAYO_PREP

    -- Unlock all POIs and set playthrough status to skip setup
    HeistHelper.set_stat(stats.BS_GEN, -1)              -- -1 unlocks all POIs
    HeistHelper.set_stat(stats.BS_ENTR, prep_vals.BS_ENTR)
    HeistHelper.set_stat(stats.BS_ABIL, prep_vals.BS_ABIL)
    HeistHelper.set_stat(stats.APPROACH, -1)            -- -1 unlocks all approaches
    HeistHelper.set_stat(stats.PLAYTHROUGH, 10)         -- Skip setup missions

    -- Set heist options
    HeistHelper.set_stat(stats.PROGRESS, difficulty)
    HeistHelper.set_stat(stats.MISSIONS, approach)
    HeistHelper.set_stat(stats.WEAPONS, loadout)
    HeistHelper.set_stat(stats.TARGET, target)

    -- Set compound loot
    local compound_stat = stats["LOOT_" .. compound_loot .. "_C"]
    if compound_stat then
        HeistHelper.set_stat(compound_stat, compound_amount)
        HeistHelper.set_stat(stats["LOOT_" .. compound_loot .. "_C_SCOPED"], compound_amount)
    end

    -- Set island loot
    local island_stat = stats["LOOT_" .. island_loot .. "_I"]
    if island_stat then
        HeistHelper.set_stat(island_stat, island_amount)
        HeistHelper.set_stat(stats["LOOT_" .. island_loot .. "_I_SCOPED"], island_amount)
    end

    -- Complete all equipment preps
    HeistHelper.set_stat(stats.UNIFORM, -1)            -- -1 unlocks all
    HeistHelper.set_stat(stats.GRAPPEL, -1)
    HeistHelper.set_stat(stats.TROJAN, prep_vals.TROJAN)

    -- Disruption preps
    HeistHelper.set_stat(stats.WEP_DISRP, prep_vals.DISRUPT)
    HeistHelper.set_stat(stats.ARM_DISRP, prep_vals.DISRUPT)
    HeistHelper.set_stat(stats.HEL_DISRP, prep_vals.DISRUPT)

    log_info("Cayo preps completed")
    show_notify(_T("Cayo Perico preps completed"), "Success")
    return true
end

local function cayo_reset_cooldown()
    if not HeistHelper then return false end

    local stats = heist_data.STATS.CAYO
    HeistHelper.set_stat(stats.TARGET_POSIX, 0)
    HeistHelper.set_stat(stats.COOLDOWN, 0)
    HeistHelper.set_stat(stats.COOLDOWN_HARD, 0)

    show_notify(_T("Cayo cooldown reset"), "Success")
    return true
end

local function cayo_reload_board()
    if not HeistHelper then return false end

    local planning_script = heist_data.SCRIPTS.ISLAND_PLANNING
    local local_index = heist_data.LOCALS.PLANNING.ISLAND

    if HeistHelper.is_script_running(planning_script) then
        HeistHelper.reload_board(planning_script, local_index)
        show_notify(_T("Board reloaded"), "Success")
        return true
    else
        show_notify(_T("Planning script not running"), "Warning")
        return false
    end
end

local function apply_cayo_cuts(cuts)
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    if not globals or not globals.CUTS or not globals.CUTS.CAYO then return false end

    HeistHelper.set_global(globals.CUTS.CAYO.PLAYER1, cuts[1] or 100)
    HeistHelper.set_global(globals.CUTS.CAYO.PLAYER2, cuts[2] or 100)
    HeistHelper.set_global(globals.CUTS.CAYO.PLAYER3, cuts[3] or 100)
    HeistHelper.set_global(globals.CUTS.CAYO.PLAYER4, cuts[4] or 100)

    show_notify(_T("Cayo cuts applied"), "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Diamond Casino Functions]
-- -------------------------------------------------------------------------

local function apply_casino_cuts(cuts)
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    if not globals or not globals.CUTS or not globals.CUTS.CASINO then return false end

    HeistHelper.set_global(globals.CUTS.CASINO.PLAYER1, cuts[1] or 100)
    HeistHelper.set_global(globals.CUTS.CASINO.PLAYER2, cuts[2] or 100)
    HeistHelper.set_global(globals.CUTS.CASINO.PLAYER3, cuts[3] or 100)
    HeistHelper.set_global(globals.CUTS.CASINO.PLAYER4, cuts[4] or 100)

    show_notify(_T("Casino cuts applied"), "Success")
    return true
end

local function casino_complete_preps(difficulty, approach, gunman, driver, hacker, masks, keycards, guards, target)
    if not HeistHelper then return false end

    local stats = heist_data.STATS.CASINO

    -- Reset last approach first
    HeistHelper.set_stat(stats.LAST_APPROACH, 0)

    HeistHelper.set_stat(stats.HARD_APPROACH, difficulty)
    HeistHelper.set_stat(stats.APPROACH, approach)
    HeistHelper.set_stat(stats.CREWWEAP, gunman)
    HeistHelper.set_stat(stats.CREWDRIVER, driver)
    HeistHelper.set_stat(stats.CREWHACKER, hacker)
    HeistHelper.set_stat(stats.MASKS, masks)
    HeistHelper.set_stat(stats.KEYLEVELS, keycards)
    HeistHelper.set_stat(stats.DISRUPTSHIP, guards)
    HeistHelper.set_stat(stats.TARGET, target)

    -- Complete all optional preps (-1 unlocks all)
    HeistHelper.set_stat(stats.BITSET0, -1)
    HeistHelper.set_stat(stats.BITSET1, -1)
    HeistHelper.set_stat(stats.POI, -1)
    HeistHelper.set_stat(stats.ACCESSPOINTS, -1)
    HeistHelper.set_stat(stats.BODYARMORLVL, -1)

    -- Complete heist flow
    HeistHelper.set_stat(stats.HEIST_NOTS, -1)
    HeistHelper.set_stat(stats.HEIST_FLOW, -1)
    HeistHelper.set_stat(stats.COMPLETEDPOSIX, -1)

    -- Also set finale globals (merged from casino_apply_all_data)
    local data = heist_data.GLOBALS.CASINO.DATA
    if data then
        if data.TARGET then HeistHelper.set_global(data.TARGET, target) end
        if data.GUNMAN then HeistHelper.set_global(data.GUNMAN, gunman) end
        if data.DRIVER then HeistHelper.set_global(data.DRIVER, driver) end
        if data.HACKER then HeistHelper.set_global(data.HACKER, hacker) end
        if data.HARDMODE then HeistHelper.set_global(data.HARDMODE, difficulty) end

        -- Set default values for other finale options
        if data.CAMERAS then HeistHelper.set_global(data.CAMERAS, 1) end
        if data.PATROL then HeistHelper.set_global(data.PATROL, 1) end
        if data.GUARDS then HeistHelper.set_global(data.GUARDS, guards) end
        if data.NVDS then HeistHelper.set_global(data.NVDS, 1) end
        if data.DRILLS then HeistHelper.set_global(data.DRILLS, 1) end
        if data.KEYCARDS then HeistHelper.set_global(data.KEYCARDS, keycards) end
        if data.GEAR then HeistHelper.set_global(data.GEAR, 1) end
    end

    log_info("Casino preps completed (stats + globals)")
    show_notify(_T("Diamond Casino preps completed"), "Success")
    return true
end

local function casino_reset_cooldown()
    if not HeistHelper then return false end

    local stats = heist_data.STATS.CASINO
    HeistHelper.set_stat(stats.COOLDOWN, 0)
    HeistHelper.set_stat(stats.COMPLETEDPOSIX, 0)

    show_notify(_T("Casino cooldown reset"), "Success")
    return true
end

local function casino_reset_preps()
    if not HeistHelper then return false end

    local stats = heist_data.STATS.CASINO
    local reload_vals = heist_data.VALUES

    -- Reset all casino heist stats
    HeistHelper.set_stat(stats.LAST_APPROACH, 0)
    HeistHelper.set_stat(stats.HARD_APPROACH, 0)
    HeistHelper.set_stat(stats.APPROACH, 0)
    HeistHelper.set_stat(stats.OPT_APPROACH, 0)
    HeistHelper.set_stat(stats.CREWWEAP, 0)
    HeistHelper.set_stat(stats.WEAPS, 0)
    HeistHelper.set_stat(stats.CREWDRIVER, 0)
    HeistHelper.set_stat(stats.VEHS, 0)
    HeistHelper.set_stat(stats.CREWHACKER, 0)
    HeistHelper.set_stat(stats.MASKS, 0)
    HeistHelper.set_stat(stats.TARGET, 0)
    HeistHelper.set_stat(stats.DISRUPTSHIP, 0)
    HeistHelper.set_stat(stats.BODYARMORLVL, 1)
    HeistHelper.set_stat(stats.KEYLEVELS, 0)
    HeistHelper.set_stat(stats.BITSET0, 0)
    HeistHelper.set_stat(stats.BITSET1, 0)
    HeistHelper.set_stat(stats.POI, 0)
    HeistHelper.set_stat(stats.ACCESSPOINTS, 0)
    HeistHelper.set_stat(stats.HEIST_NOTS, 0)
    HeistHelper.set_stat(stats.HEIST_FLOW, 0)
    HeistHelper.set_stat(stats.BOARD_DIALOGUE0, 0)
    HeistHelper.set_stat(stats.BOARD_DIALOGUE1, 0)
    HeistHelper.set_stat(stats.BOARD_DIALOGUE2, 0)
    HeistHelper.set_stat(stats.COOLDOWN, 0)
    HeistHelper.set_stat(stats.COMPLETEDPOSIX, 0)

    -- Reload planning board
    local planning_script = heist_data.SCRIPTS.CASINO_PLANNING
    local local_index = heist_data.LOCALS.PLANNING.CASINO

    if HeistHelper.is_script_running(planning_script) then
        HeistHelper.set_local(planning_script, local_index, reload_vals.BOARD_RELOAD)
    end

    show_notify(_T("Casino preps reset"), "Success")
    return true
end

local function casino_reload_board()
    if not HeistHelper then return false end

    local planning_script = heist_data.SCRIPTS.CASINO_PLANNING
    local local_index = heist_data.LOCALS.PLANNING.CASINO

    if HeistHelper.is_script_running(planning_script) then
        HeistHelper.reload_board(planning_script, local_index)
        show_notify(_T("Casino boards reloaded"), "Success")
        return true
    else
        show_notify(_T("Planning script not running"), "Warning")
        return false
    end
end

-- -------------------------------------------------------------------------
-- [Auto Shop Functions]
-- -------------------------------------------------------------------------

local function autoshop_complete_preps(contract)
    if not HeistHelper then return false end

    local stats = heist_data.STATS.AUTOSHOP

    HeistHelper.set_stat(stats.CURRENT, contract)
    local gen_bs = (contract == 1) and 4351 or 12543
    HeistHelper.set_stat(stats.GEN_BS, gen_bs)

    log_info("Auto Shop preps completed")
    show_notify(_T("Auto Shop preps completed"), "Success")
    return true
end

local function autoshop_set_payout(payout)
    if not HeistHelper then return false end

    local tunables = heist_data.TUNABLES.AUTOSHOP_PAYOUT
    for _, tunable_name in ipairs(tunables) do
        HeistHelper.set_tunable(tunable_name, payout)
    end

    log_info("Auto Shop payout set to: " .. payout)
    show_notify(_T("Auto Shop payout set to: ") .. "$" .. payout, "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Solo Launch Functions]
-- -------------------------------------------------------------------------

local function solo_launch_generic()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS.LAUNCHER
    local locals = heist_data.LOCALS.LAUNCHER

    if not HeistHelper.is_script_running(scripts.LAUNCHER) then
        show_notify(_T("Launcher script not running"), "Warning")
        return false
    end

    -- Get current heist value from local
    local value = HeistHelper.get_local(scripts.LAUNCHER, locals.STATUS)
    if not value or value == 0 then
        show_notify(_T("No active heist found"), "Warning")
        return false
    end

    -- Set player count to solo (from data)
    -- Formula: BASE_LOBBY + 4 + 1 + (value * 95) + 75 controls player requirement
    local offset = heist_data.VALUES.PLAYER_COUNT_OFFSET
    local player_count_global = globals.BASE_LOBBY + offset.BASE + 1 + (value * offset.MULTIPLIER) + offset.FINAL_OFFSET
    HeistHelper.set_global(player_count_global, globals.PLAYER_COUNT.SOLO)

    -- Set launcher locals
    HeistHelper.set_local(scripts.LAUNCHER, locals.PLAYER_COUNT, globals.PLAYER_COUNT.SOLO)

    -- Set launcher globals (values from data)
    local solo_values = globals.SOLO_VALUES
    HeistHelper.set_global(globals.STEP_1, solo_values.STEP_1)
    HeistHelper.set_global(globals.STEP_2, solo_values.STEP_2)
    HeistHelper.set_global(globals.STEP_3, solo_values.STEP_3)
    HeistHelper.set_global(globals.STEP_4, solo_values.STEP_4)
    HeistHelper.set_global(globals.STEP_5, solo_values.STEP_5)

    -- Set timer local
    HeistHelper.set_local(scripts.LAUNCHER, locals.TIMER, 0)

    return true
end

local function solo_launch_casino_setup()
    if not HeistHelper or not heist_data or not heist_data.GLOBALS then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS.CASINO
    local stats = heist_data.STATS.CASINO

    if not HeistHelper.is_script_running(scripts.MISSION_OLD) then
        return false
    end

    local is_finale = HeistHelper.get_global(heist_data.GLOBALS.CASINO.FINALE_CHECK)
    if not is_finale or is_finale ~= 1 then
        return false
    end

    -- Get approach type
    local approach = HeistHelper.get_stat(stats.APPROACH)
    if not approach then return false end

    -- Set casino-specific data for finale
    if approach == 2 then  -- Big Con
        -- Set van type for Big Con approach
        HeistHelper.set_global(globals.DATA.VAN, globals.FINALE_VALUES.VAN_BIG_CON)
    end

    -- Set target from stat
    local target = HeistHelper.get_stat(stats.TARGET) or 0
    HeistHelper.set_global(globals.DATA.TARGET, target)

    return true
end

local function solo_launch_reset_apartment()
    if not HeistHelper or not heist_data or not heist_data.GLOBALS then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS.LAUNCHER
    local locals = heist_data.LOCALS.LAUNCHER

    if not HeistHelper.is_script_running(scripts.LAUNCHER) then
        return false
    end

    local value = HeistHelper.get_local(scripts.LAUNCHER, locals.STATUS)
    if not value or value == 0 then return false end

    -- Apartment: Check if it's Fleeca or others (use player count from data)
    local stat = HeistHelper.get_stat("HEIST_MISSION_RCONT_ID_1")
    local is_fleeca = (stat == 1)
    local required_players = is_fleeca and globals.PLAYER_COUNT.FLEECA or globals.PLAYER_COUNT.APARTMENT

    local offset = heist_data.VALUES.PLAYER_COUNT_OFFSET
    local player_count_global = globals.BASE_LOBBY + offset.BASE + 1 + (value * offset.MULTIPLIER) + offset.FINAL_OFFSET
    HeistHelper.set_global(player_count_global, required_players)
    HeistHelper.set_local(scripts.LAUNCHER, locals.PLAYER_COUNT, required_players)
    HeistHelper.set_global(globals.STEP_1, required_players)
    HeistHelper.set_global(globals.STEP_2, required_players)

    -- Use values from data
    local reset_values = globals.APARTMENT_RESET
    HeistHelper.set_global(globals.STEP_3, reset_values.STEP_3)
    HeistHelper.set_global(globals.STEP_4, reset_values.STEP_4)
    HeistHelper.set_local(scripts.LAUNCHER, locals.TIMER, 0)
    HeistHelper.set_global(globals.STEP_5, reset_values.STEP_5)

    return true
end

local function solo_launch_reset_casino()
    if not HeistHelper or not heist_data or not heist_data.GLOBALS then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS.LAUNCHER
    local locals = heist_data.LOCALS.LAUNCHER

    if not HeistHelper.is_script_running(scripts.LAUNCHER) then
        return false
    end

    local value = HeistHelper.get_local(scripts.LAUNCHER, locals.STATUS)
    if not value or value == 0 then return false end

    -- Casino: Always 2 players, use reset values from data
    local offset = heist_data.VALUES.PLAYER_COUNT_OFFSET
    local player_count_global = globals.BASE_LOBBY + offset.BASE + 1 + (value * offset.MULTIPLIER) + offset.FINAL_OFFSET
    HeistHelper.set_global(player_count_global, globals.PLAYER_COUNT.DUO)
    HeistHelper.set_local(scripts.LAUNCHER, locals.PLAYER_COUNT, globals.PLAYER_COUNT.DUO)

    local reset_values = globals.CASINO_RESET
    HeistHelper.set_global(globals.STEP_1, reset_values.STEP_1)
    HeistHelper.set_global(globals.STEP_2, reset_values.STEP_2)
    HeistHelper.set_global(globals.STEP_3, reset_values.STEP_3)
    HeistHelper.set_global(globals.STEP_4, reset_values.STEP_4)

    return true
end

local function solo_launch_reset_doomsday()
    if not HeistHelper or not heist_data or not heist_data.GLOBALS then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS.LAUNCHER
    local locals = heist_data.LOCALS.LAUNCHER

    if not HeistHelper.is_script_running(scripts.LAUNCHER) then
        return false
    end

    local value = HeistHelper.get_local(scripts.LAUNCHER, locals.STATUS)
    if not value or value == 0 then return false end

    -- Doomsday: Always 2 players, use reset values from data
    local offset = heist_data.VALUES.PLAYER_COUNT_OFFSET
    local player_count_global = globals.BASE_LOBBY + offset.BASE + 1 + (value * offset.MULTIPLIER) + offset.FINAL_OFFSET
    HeistHelper.set_global(player_count_global, globals.PLAYER_COUNT.DUO)
    HeistHelper.set_local(scripts.LAUNCHER, locals.PLAYER_COUNT, globals.PLAYER_COUNT.DUO)

    local reset_values = globals.DOOMSDAY_RESET
    HeistHelper.set_global(globals.STEP_1, reset_values.STEP_1)
    HeistHelper.set_global(globals.STEP_2, reset_values.STEP_2)
    HeistHelper.set_global(globals.STEP_3, reset_values.STEP_3)
    HeistHelper.set_global(globals.STEP_4, reset_values.STEP_4)

    return true
end

-- -------------------------------------------------------------------------
-- [Mission Assist Functions]
-- -------------------------------------------------------------------------

local function assist_skip_fingerprint()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    -- Casino fingerprint hack
    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.FINGERPRINT_HACK, hack_vals.FINGERPRINT)
        show_notify(_T("Fingerprint hack skipped"), "Success")
        return true
    end

    -- Cayo fingerprint hack
    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.FINGERPRINT_HACK, hack_vals.FINGERPRINT)
        show_notify(_T("Fingerprint hack skipped"), "Success")
        return true
    end

    -- Cayo island fingerprint
    if HeistHelper.is_script_running(scripts.ISLAND_HEIST) then
        local target = HeistHelper.get_local(scripts.ISLAND_HEIST, locals.ISLAND_HEIST.VOLTLAB_TARGET)
        if target then
            HeistHelper.set_local(scripts.ISLAND_HEIST, locals.ISLAND_HEIST.VOLTLAB_CURRENT, target)
            HeistHelper.set_local(scripts.ISLAND_HEIST, locals.ISLAND_HEIST.VOLTLAB_STATE, hack_vals.VOLTLAB)
            show_notify(_T("Fingerprint hack skipped"), "Success")
            return true
        end
    end

    show_notify(_T("No active hack found"), "Warning")
    return false
end

local function assist_skip_keypad()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.KEYPAD_HACK, hack_vals.KEYPAD)
        show_notify(_T("Keypad hack skipped"), "Success")
        return true
    end

    show_notify(_T("No active hack found"), "Warning")
    return false
end

local function assist_skip_vault_drill()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        -- Copy VaultDrill2 value to VaultDrill1
        local drill2_value = HeistHelper.get_local(scripts.MISSION_OLD, locals.MISSION_OLD.VAULT_DRILL_2)
        if drill2_value then
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.VAULT_DRILL_1, drill2_value)
        end
        show_notify(_T("Vault drill completed"), "Success")
        return true
    end

    show_notify(_T("Drill not active"), "Warning")
    return false
end

local function assist_skip_plasma_cutter()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.PLASMA_CUTTER, hack_vals.PLASMA_CUTTER, "float")
        show_notify(_T("Plasma cutter completed"), "Success")
        return true
    end

    show_notify(_T("Plasma cutter not active"), "Warning")
    return false
end

local function assist_skip_drainage_pipe()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.DRAINAGE_PIPE, hack_vals.DRAINAGE)
        show_notify(_T("Drainage pipe cut completed"), "Success")
        return true
    end

    show_notify(_T("Drainage pipe not active"), "Warning")
    return false
end

local function assist_skip_fleeca_hack()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.FLEECA_HACK, hack_vals.FLEECA)
        show_notify(_T("Fleeca hack completed"), "Success")
        return true
    end

    show_notify(_T("Hack not active"), "Warning")
    return false
end

local function assist_skip_fleeca_drill()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.FLEECA_DRILL, hack_vals.FLEECA_DRILL, "float")
        show_notify(_T("Fleeca drill completed"), "Success")
        return true
    end

    show_notify(_T("Drill not active"), "Warning")
    return false
end

local function assist_skip_pacific_hack()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.PACIFIC_HACK, hack_vals.PACIFIC)
        show_notify(_T("Pacific hack completed"), "Success")
        return true
    end

    show_notify(_T("Hack not active"), "Warning")
    return false
end

local function assist_skip_data_hack()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DATA_HACK, hack_vals.DATA)
        show_notify(_T("Data hack completed"), "Success")
        return true
    end

    show_notify(_T("Hack not active"), "Warning")
    return false
end

local function assist_skip_doomsday_hack()
    if not HeistHelper then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local hack_vals = heist_data.VALUES.HACK_BYPASS

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DOOMSDAY_HACK, hack_vals.DOOMSDAY)
        show_notify(_T("Doomsday hack completed"), "Success")
        return true
    end

    show_notify(_T("Hack not active"), "Warning")
    return false
end

-- Instant Finish functions

local function assist_instant_finish_cayo()
    if not HeistHelper then return false end
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local finish_vals = heist_data.VALUES.FINISH

    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        util.create_job(function()
            HeistHelper.force_host(scripts.MISSION_NEW)
            util.yield(1000)

            HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.CAYO_FINISH_STEP1, finish_vals.CAYO_STEP1)
            HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.CAYO_FINISH_STEP2, finish_vals.CAYO_STEP2)

            show_notify(_T("Cayo Perico instant finish"), "Success")
        end)
        return true
    end
    show_notify(_T("Cayo script not running"), "Warning")
    return false
end

local function assist_instant_finish_casino()
    if not HeistHelper then return false end
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local finish_vals = heist_data.VALUES.FINISH

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        util.create_job(function()
            HeistHelper.force_host(scripts.MISSION_OLD)
            util.yield(1000)

            local approach = HeistHelper.get_stat("H3OPT_APPROACH") or 1

            if approach == 3 then
                -- Aggressive approach
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP1, finish_vals.CASINO_AGGRESSIVE_STEP1)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP3, finish_vals.APARTMENT_STEP3)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP4, finish_vals.CASINO_STEP4_MONEY)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP5, finish_vals.APARTMENT_STEP5)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP6, finish_vals.APARTMENT_STEP6)
            else
                -- Silent & Sneaky or Big Con
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP2, finish_vals.APARTMENT_STEP2)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP3, finish_vals.APARTMENT_STEP3)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP4, finish_vals.APARTMENT_STEP4_MONEY)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP5, finish_vals.APARTMENT_STEP5)
                HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP6, finish_vals.APARTMENT_STEP6)
            end

            show_notify(_T("Diamond Casino instant finish"), "Success")
        end)
        return true
    end
    show_notify(_T("Casino script not running"), "Warning")
    return false
end

local function assist_instant_finish_doomsday()
    if not HeistHelper then return false end
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local finish_vals = heist_data.VALUES.FINISH

    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        util.create_job(function()
            HeistHelper.force_host(scripts.MISSION_OLD)
            util.yield(1000)

            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DOOMSDAY_FINISH_STEP1, finish_vals.DOOMSDAY_STEP1)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DOOMSDAY_FINISH_STEP2, finish_vals.DOOMSDAY_STEP2)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DOOMSDAY_FINISH_STEP3, finish_vals.DOOMSDAY_STEP3)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DOOMSDAY_FINISH_STEP4, finish_vals.DOOMSDAY_STEP4)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.DOOMSDAY_FINISH_STEP5, finish_vals.DOOMSDAY_STEP5)

            show_notify(_T("Doomsday instant finish"), "Success")
        end)
        return true
    end
    show_notify(_T("Doomsday script not running"), "Warning")
    return false
end

local function assist_instant_finish_agency()
    if not HeistHelper then return false end
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local finish_vals = heist_data.VALUES.FINISH

    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        HeistHelper.force_host(scripts.MISSION_NEW)

        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.AGENCY_FINISH_STEP1, finish_vals.AGENCY_STEP1)
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.AGENCY_FINISH_STEP2, finish_vals.AGENCY_STEP2)

        show_notify(_T("Agency instant finish"), "Success")
        return true
    end
    show_notify(_T("Agency script not running"), "Warning")
    return false
end

local function assist_instant_finish_autoshop()
    if not HeistHelper then return false end
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local finish_vals = heist_data.VALUES.FINISH

    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        HeistHelper.force_host(scripts.MISSION_NEW)

        -- Set instant finish locals
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.AUTOSHOP_FINISH_STEP1, finish_vals.AUTOSHOP_STEP1)
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.AUTOSHOP_FINISH_STEP2, finish_vals.AUTOSHOP_STEP2)

        show_notify(_T("Auto Shop instant finish"), "Success")
        return true
    end
    show_notify(_T("Auto Shop script not running"), "Warning")
    return false
end

-- -------------------------------------------------------------------------
-- [Generic Functions]
-- -------------------------------------------------------------------------

local function generic_skip_cutscene()
    if not N.STOP_CUTSCENE_IMMEDIATELY then
        show_notify(_T("Native not available"), "Error")
        return false
    end

    pcall(function()
        N.STOP_CUTSCENE_IMMEDIATELY()
    end)
    show_notify(_T("Cutscene skipped"), "Success")
    return true
end

local function generic_skip_checkpoint()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS

    -- Try old mission controller
    if HeistHelper.is_script_running(scripts.MISSION_OLD) then
        local current = HeistHelper.get_local(scripts.MISSION_OLD, locals.MISSION_OLD.SKIP_OLD) or 0
        -- Set bit 17
        local new_value = current | (1 << 17)
        HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.SKIP_OLD, new_value)
        show_notify(_T("Checkpoint skipped"), "Success")
        return true
    end

    -- Try new mission controller
    if HeistHelper.is_script_running(scripts.MISSION_NEW) then
        local current = HeistHelper.get_local(scripts.MISSION_NEW, locals.MISSION_NEW.SKIP_NEW) or 0
        local new_value = current | (1 << 17)
        HeistHelper.set_local(scripts.MISSION_NEW, locals.MISSION_NEW.SKIP_NEW, new_value)
        show_notify(_T("Checkpoint skipped"), "Success")
        return true
    end

    show_notify(_T("No mission running"), "Warning")
    return false
end

local function generic_apply_cut(cut_value)
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    if not globals.CUTS or not globals.CUTS.GENERIC then
        show_notify(_T("Generic cut global not found"), "Error")
        return false
    end

    -- Validate cut value (0-999)
    if cut_value < 0 or cut_value > 999 then
        show_notify(_T("Cut must be between 0 and 999"), "Error")
        return false
    end

    HeistHelper.set_global(globals.CUTS.GENERIC, cut_value)
    show_notify(_T("Generic cut applied: ") .. tostring(cut_value) .. "%", "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Apartment Heist Functions]
-- -------------------------------------------------------------------------

local function apartment_complete_preps()
    if not HeistHelper then return false end

    HeistHelper.set_stat("HEIST_PLANNING_STAGE", -1)
    show_notify(_T("Apartment preps completed"), "Success")
    return true
end

local function apartment_redraw_board()
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    local board_vals = heist_data.VALUES
    HeistHelper.set_global(globals.CLASSIC_HEIST.BOARD, board_vals.BOARD_CLASSIC)
    show_notify(_T("Board redrawn"), "Success")
    return true
end

local function apartment_force_ready()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS
    local ready_vals = heist_data.VALUES

    -- Force script host first
    HeistHelper.force_host(scripts.MISSION_OLD)

    util.create_job(function()
        util.yield(1000)

        -- Force all players ready
        if globals.APARTMENT_READY then
            HeistHelper.set_global(globals.APARTMENT_READY.PLAYER2, ready_vals.READY_STATE_APARTMENT)
            HeistHelper.set_global(globals.APARTMENT_READY.PLAYER3, ready_vals.READY_STATE_APARTMENT)
            HeistHelper.set_global(globals.APARTMENT_READY.PLAYER4, ready_vals.READY_STATE_APARTMENT)
        end

        show_notify(_T("All players ready"), "Success")
    end)
    return true
end

local function apartment_kill_cooldown()
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    HeistHelper.set_global(globals.CLASSIC_HEIST.COOLDOWN_BASE, -1)
    show_notify(_T("Cooldown reset"), "Success")
    return true
end

local function apartment_play_unavailable()
    if not HeistHelper or not heist_data then return false end

    -- Set cooldown to -1 to make unavailable jobs playable temporarily
    -- Note: Requires PLAYER_ID to be available (usually from globals or script)
    local player_id = 0  -- Default to player 0 (local player)
    local cooldown_global = 1877303 + 1 + (player_id * 77) + 76

    HeistHelper.set_global(cooldown_global, -1)
    show_notify(_T("Unavailable jobs now playable"), "Success")
    return true
end

local function apartment_unlock_all_jobs()
    if not HeistHelper or not heist_data then return false end

    local stats = heist_data.STATS.APARTMENT
    local tunables = heist_data.TUNABLES.APARTMENT
    local strand_vals = heist_data.VALUES

    -- Get tunable hashes for each heist
    local fleeca_hash = HeistHelper.get_tunable(tunables.ROOT_ID_HASH_FLEECA)
    local prison_hash = HeistHelper.get_tunable(tunables.ROOT_ID_HASH_PRISON)
    local humane_hash = HeistHelper.get_tunable(tunables.ROOT_ID_HASH_HUMANE)
    local series_hash = HeistHelper.get_tunable(tunables.ROOT_ID_HASH_SERIES_A)
    local pacific_hash = HeistHelper.get_tunable(tunables.ROOT_ID_HASH_PACIFIC)

    -- Unlock all heists (STRAND_0 through STRAND_4)
    -- Set root ID hash and level to 5 (completed)
    if fleeca_hash then
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_0, fleeca_hash)
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_0_L, strand_vals.STRAND_COMPLETE)
    end

    if prison_hash then
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_1, prison_hash)
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_1_L, strand_vals.STRAND_COMPLETE)
    end

    if humane_hash then
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_2, humane_hash)
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_2_L, strand_vals.STRAND_COMPLETE)
    end

    if series_hash then
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_3, series_hash)
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_3_L, strand_vals.STRAND_COMPLETE)
    end

    if pacific_hash then
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_4, pacific_hash)
        HeistHelper.set_stat(stats.HEIST_SAVED_STRAND_4_L, strand_vals.STRAND_COMPLETE)
    end

    show_notify(_T("All jobs unlocked - restart game"), "Success")
    return true
end

local function apartment_change_session()
    -- Note: This requires native game functions to change session
    -- Lexis might not have direct session control, so we'll show a message
    show_notify(_T("Change session manually via game menu"), "Info")
    return true
end

local function apartment_instant_finish()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS
    local finish_vals = heist_data.VALUES.FINISH

    if not HeistHelper.is_script_running(scripts.MISSION_OLD) then
        show_notify(_T("Apartment script not running"), "Warning")
        return false
    end

    util.create_job(function()
        HeistHelper.force_host(scripts.MISSION_OLD)
        util.yield(1000)

        -- Check if Pacific Standard
        local heist = HeistHelper.get_stat("HEIST_MISSION_RCONT_ID_1") or 0
        local is_pacific = (heist == 5)  -- Pacific Standard Job

        if is_pacific then
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP2, finish_vals.APARTMENT_STEP2)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP3, finish_vals.APARTMENT_STEP3)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP4, finish_vals.APARTMENT_STEP4_MONEY)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP5, finish_vals.APARTMENT_STEP5)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP6, finish_vals.APARTMENT_STEP6)
        else
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP1, finish_vals.APARTMENT_STEP1)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP4, finish_vals.APARTMENT_STEP4_ALT)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP5, finish_vals.APARTMENT_STEP5)
            HeistHelper.set_local(scripts.MISSION_OLD, locals.MISSION_OLD.APARTMENT_FINISH_STEP6, finish_vals.APARTMENT_STEP6)
        end

        show_notify(_T("Apartment instant finish"), "Success")
    end)
    return true
end

local function apartment_12mil_bonus(enable)
    if not HeistHelper then return false end

    local stats = heist_data.STATS.CLASSIC_BONUS
    if enable then
        HeistHelper.set_stat_raw(stats.FLOW_ORDER, 268435455)
        HeistHelper.set_stat_raw(stats.AWD_ORDER, 0)
        HeistHelper.set_stat_raw(stats.TEAM_PROGRESS, 268435455)
        HeistHelper.set_stat_raw(stats.AWD_TEAM, 0)
        HeistHelper.set_stat_raw(stats.NO_DEATH, 268435455)
        HeistHelper.set_stat_raw(stats.AWD_ULT, 0)
        show_notify(_T("12mil bonus enabled"), "Success")
    else
        HeistHelper.set_stat_raw(stats.FLOW_ORDER, 134217727)
        HeistHelper.set_stat_raw(stats.AWD_ORDER, 1)
        HeistHelper.set_stat_raw(stats.TEAM_PROGRESS, 134217727)
        HeistHelper.set_stat_raw(stats.AWD_TEAM, 1)
        HeistHelper.set_stat_raw(stats.NO_DEATH, 134217727)
        HeistHelper.set_stat_raw(stats.AWD_ULT, 1)
        show_notify(_T("12mil bonus disabled"), "Success")
    end
    return true
end

local function apply_apartment_cuts(cuts)
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    if not globals or not globals.CUTS then return false end

    -- Apartment cuts: Leader (Player1) gets remainder (100 - total)
    -- Can be negative if total > 100 (allows other players to get more)
    local total = (cuts[1] or 0) + (cuts[2] or 0) + (cuts[3] or 0) + (cuts[4] or 0)

    -- Set globals for all players
    local base = globals.CUTS.APARTMENT
    HeistHelper.set_global(base + 1 + 1, 100 - total)
    HeistHelper.set_global(base + 1 + 2, cuts[2] or 0)
    HeistHelper.set_global(base + 1 + 3, cuts[3] or 0)
    HeistHelper.set_global(base + 1 + 4, cuts[4] or 0)

    -- Set Player1 local cut value
    HeistHelper.set_global(globals.CUTS.APARTMENT_LOCAL, cuts[1] or 0)

    show_notify(_T("Apartment cuts applied"), "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Force Ready Functions]
-- -------------------------------------------------------------------------

local function cayo_force_ready()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS
    local ready_vals = heist_data.VALUES

    util.create_job(function()
        HeistHelper.force_host(scripts.MISSION_NEW)
        util.yield(1000)

        if globals.CAYO_READY then
            HeistHelper.set_global(globals.CAYO_READY.PLAYER2, ready_vals.READY_STATE_HEIST)
            HeistHelper.set_global(globals.CAYO_READY.PLAYER3, ready_vals.READY_STATE_HEIST)
            HeistHelper.set_global(globals.CAYO_READY.PLAYER4, ready_vals.READY_STATE_HEIST)
        end

        show_notify(_T("All players ready"), "Success")
    end)
    return true
end

local function casino_force_ready()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS
    local ready_vals = heist_data.VALUES

    util.create_job(function()
        HeistHelper.force_host(scripts.MISSION_OLD)
        util.yield(1000)

        if globals.CASINO_READY then
            HeistHelper.set_global(globals.CASINO_READY.PLAYER2, ready_vals.READY_STATE_HEIST)
            HeistHelper.set_global(globals.CASINO_READY.PLAYER3, ready_vals.READY_STATE_HEIST)
            HeistHelper.set_global(globals.CASINO_READY.PLAYER4, ready_vals.READY_STATE_HEIST)
        end

        show_notify(_T("All players ready"), "Success")
    end)
    return true
end

local function doomsday_force_ready()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local globals = heist_data.GLOBALS
    local ready_vals = heist_data.VALUES

    util.create_job(function()
        HeistHelper.force_host(scripts.MISSION_OLD)
        util.yield(1000)

        if globals.DOOMSDAY_READY then
            HeistHelper.set_global(globals.DOOMSDAY_READY.PLAYER2, ready_vals.READY_STATE_HEIST)
            HeistHelper.set_global(globals.DOOMSDAY_READY.PLAYER3, ready_vals.READY_STATE_HEIST)
            HeistHelper.set_global(globals.DOOMSDAY_READY.PLAYER4, ready_vals.READY_STATE_HEIST)
        end

        show_notify(_T("All players ready"), "Success")
    end)
    return true
end

-- -------------------------------------------------------------------------
-- [Infinite Bag Functions]
-- -------------------------------------------------------------------------

local function cayo_infinite_bag(enable)
    if not HeistHelper then return false end

    local capacity_vals = heist_data.VALUES
    if enable then
        HeistHelper.set_tunable("IH_LOOT_BAG_MAX_CAPACITY", capacity_vals.MAX_CAPACITY)
        show_notify(_T("Infinite bag enabled"), "Success")
    else
        HeistHelper.reset_tunable("IH_LOOT_BAG_MAX_CAPACITY")
        show_notify(_T("Infinite bag disabled"), "Success")
    end
    return true
end

local function casino_infinite_bag(enable)
    if not HeistHelper then return false end

    local capacity_vals = heist_data.VALUES
    if enable then
        HeistHelper.set_tunable("CH_LOOT_BAG_LIMIT", capacity_vals.MAX_CAPACITY)
        show_notify(_T("Infinite bag enabled"), "Success")
    else
        HeistHelper.reset_tunable("CH_LOOT_BAG_LIMIT")
        show_notify(_T("Infinite bag disabled"), "Success")
    end
    return true
end

-- -------------------------------------------------------------------------
-- [Reset Preps Functions]
-- -------------------------------------------------------------------------

local function cayo_reset_preps()
    if not HeistHelper or not heist_data then return false end

    local stats = heist_data.STATS.CAYO
    local reload_vals = heist_data.VALUES

    HeistHelper.set_stat(stats.PROGRESS, 0)
    HeistHelper.set_stat(stats.MISSIONS, 0)
    HeistHelper.set_stat(stats.APPROACH, 0)
    HeistHelper.set_stat(stats.TARGET, -1)
    HeistHelper.set_stat(stats.BS_GEN, 0)
    HeistHelper.set_stat(stats.BS_ENTR, 0)
    HeistHelper.set_stat(stats.BS_ABIL, 0)
    HeistHelper.set_stat(stats.PLAYTHROUGH, 0)

    -- Reload planning screen
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    if HeistHelper.is_script_running(scripts.ISLAND_PLANNING) then
        HeistHelper.set_local(scripts.ISLAND_PLANNING, locals.ISLAND, reload_vals.BOARD_RELOAD)
    end

    show_notify(_T("Cayo preps reset"), "Success")
    return true
end

local function autoshop_reset_preps()
    if not HeistHelper or not heist_data then return false end

    local stats = heist_data.STATS.AUTOSHOP
    local reload_vals = heist_data.VALUES
    HeistHelper.set_stat(stats.GEN_BS, 12467)

    -- Reload board
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    if HeistHelper.is_script_running(scripts.TUNER_PLANNING) then
        HeistHelper.set_local(scripts.TUNER_PLANNING, locals.TUNER, reload_vals.BOARD_RELOAD)
    end

    show_notify(_T("Auto Shop preps reset"), "Success")
    return true
end

local function autoshop_redraw_board()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    local reload_vals = heist_data.VALUES

    if HeistHelper.is_script_running(scripts.TUNER_PLANNING) then
        HeistHelper.set_local(scripts.TUNER_PLANNING, locals.TUNER, reload_vals.BOARD_RELOAD)
        show_notify(_T("Board redrawn"), "Success")
        return true
    end

    show_notify(_T("Not in Auto Shop"), "Warning")
    return false
end

-- -------------------------------------------------------------------------
-- [Remove Crew Cuts]
-- -------------------------------------------------------------------------

local function cayo_remove_crew_cuts(enable)
    if not HeistHelper then return false end

    if enable then
        HeistHelper.set_tunable("IH_FENCING_FEE_DIVIDE", 0)
        HeistHelper.set_tunable("IH_DEDUCTION_FENCING_FEE", 0)
        show_notify(_T("Crew cuts removed"), "Success")
    else
        HeistHelper.reset_tunable("IH_FENCING_FEE_DIVIDE")
        HeistHelper.reset_tunable("IH_DEDUCTION_FENCING_FEE")
        show_notify(_T("Crew cuts restored"), "Success")
    end
    return true
end

-- -------------------------------------------------------------------------
-- [Casino Skip Setup]
-- -------------------------------------------------------------------------

local function casino_skip_setup()
    if not HeistHelper then return false end

    -- Packed stat for Arcade setup
    HeistHelper.set_stat("ARCADE_SETUP_DONE", 1)
    show_notify(_T("Arcade setup skipped - change session"), "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Casino Remove Crew Cuts]
-- -------------------------------------------------------------------------

local function casino_remove_crew_cuts(enable)
    if not HeistHelper then return false end

    if enable then
        -- Remove all crew cuts
        HeistHelper.set_tunable("CH_CREW_CUT_MINIMUM", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_MAXIMUM", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_GUNMAN_1", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_GUNMAN_2", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_GUNMAN_3", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_GUNMAN_4", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_GUNMAN_5", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_DRIVER_1", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_DRIVER_2", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_DRIVER_3", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_DRIVER_4", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_DRIVER_5", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_HACKER_1", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_HACKER_2", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_HACKER_3", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_HACKER_4", 0)
        HeistHelper.set_tunable("CH_CREW_CUT_HACKER_5", 0)
        show_notify(_T("Crew cuts removed"), "Success")
    else
        -- Restore crew cuts
        HeistHelper.reset_tunable("CH_CREW_CUT_MINIMUM")
        HeistHelper.reset_tunable("CH_CREW_CUT_MAXIMUM")
        HeistHelper.reset_tunable("CH_CREW_CUT_GUNMAN_1")
        HeistHelper.reset_tunable("CH_CREW_CUT_GUNMAN_2")
        HeistHelper.reset_tunable("CH_CREW_CUT_GUNMAN_3")
        HeistHelper.reset_tunable("CH_CREW_CUT_GUNMAN_4")
        HeistHelper.reset_tunable("CH_CREW_CUT_GUNMAN_5")
        HeistHelper.reset_tunable("CH_CREW_CUT_DRIVER_1")
        HeistHelper.reset_tunable("CH_CREW_CUT_DRIVER_2")
        HeistHelper.reset_tunable("CH_CREW_CUT_DRIVER_3")
        HeistHelper.reset_tunable("CH_CREW_CUT_DRIVER_4")
        HeistHelper.reset_tunable("CH_CREW_CUT_DRIVER_5")
        HeistHelper.reset_tunable("CH_CREW_CUT_HACKER_1")
        HeistHelper.reset_tunable("CH_CREW_CUT_HACKER_2")
        HeistHelper.reset_tunable("CH_CREW_CUT_HACKER_3")
        HeistHelper.reset_tunable("CH_CREW_CUT_HACKER_4")
        HeistHelper.reset_tunable("CH_CREW_CUT_HACKER_5")
        show_notify(_T("Crew cuts restored"), "Success")
    end
    return true
end

-- -------------------------------------------------------------------------
-- [Doomsday Reset Preps]
-- -------------------------------------------------------------------------

local function apply_doomsday_cuts(cuts)
    if not HeistHelper or not heist_data then return false end

    local globals = heist_data.GLOBALS
    if not globals or not globals.CUTS or not globals.CUTS.DOOMSDAY then return false end

    HeistHelper.set_global(globals.CUTS.DOOMSDAY.PLAYER1, cuts[1] or 100)
    HeistHelper.set_global(globals.CUTS.DOOMSDAY.PLAYER2, cuts[2] or 100)
    HeistHelper.set_global(globals.CUTS.DOOMSDAY.PLAYER3, cuts[3] or 100)
    HeistHelper.set_global(globals.CUTS.DOOMSDAY.PLAYER4, cuts[4] or 100)

    show_notify(_T("Doomsday cuts applied"), "Success")
    return true
end

local function doomsday_reset_preps()
    if not HeistHelper or not heist_data then return false end

    local stats = heist_data.STATS.DOOMSDAY
    local reload_vals = heist_data.VALUES
    HeistHelper.set_stat(stats.FLOW_MISSION_PROG, 0)
    HeistHelper.set_stat(stats.HEIST_STATUS, 0)
    HeistHelper.set_stat(stats.FLOW_NOTIFICATIONS, 0)

    -- Reload planning board
    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    if HeistHelper.is_script_running(scripts.GANG_OPS_PLANNING) then
        HeistHelper.set_local(scripts.GANG_OPS_PLANNING, locals.GANG_OPS, reload_vals.GANG_OPS_RELOAD)
    end

    show_notify(_T("Doomsday preps reset"), "Success")
    return true
end

local function doomsday_reload_board()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    local reload_vals = heist_data.VALUES

    if HeistHelper.is_script_running(scripts.GANG_OPS_PLANNING) then
        HeistHelper.set_local(scripts.GANG_OPS_PLANNING, locals.GANG_OPS, reload_vals.GANG_OPS_RELOAD)
        show_notify(_T("Board redrawn"), "Success")
        return true
    end

    show_notify(_T("Not in Facility"), "Warning")
    return false
end

-- -------------------------------------------------------------------------
-- [AutoShop Kill Cooldown]
-- -------------------------------------------------------------------------

local function autoshop_kill_cooldown()
    if not HeistHelper then return false end

    -- Reset tuner robbery cooldown
    HeistHelper.set_tunable("TUNER_ROBBERY_COOLDOWN", 0)
    show_notify(_T("Cooldown reset"), "Success")
    return true
end

-- -------------------------------------------------------------------------
-- [Salvage Yard Functions]
-- -------------------------------------------------------------------------

local function salvage_set_slot(slot, robbery_idx, vehicle_idx)
    if not HeistHelper or not heist_data then return false end

    local tunables = heist_data.TUNABLES.SALVAGE
    local robberies = heist_data.OPTIONS.SALVAGE_ROBBERIES
    local vehicles = heist_data.OPTIONS.SALVAGE_VEHICLES

    local robbery = heist_data.get_option_value(robberies, robbery_idx)
    local vehicle = heist_data.get_option_value(vehicles, vehicle_idx)

    if slot == 1 then
        HeistHelper.set_tunable_by_hash(tunables.SLOT1_ROBBERY_TYPE, robbery)
        HeistHelper.set_tunable_by_hash(tunables.SLOT1_VEHICLE_TYPE, vehicle)
        HeistHelper.set_tunable_by_hash(tunables.SLOT1_KEEP, 1)
    elseif slot == 2 then
        HeistHelper.set_tunable_by_hash(tunables.SLOT2_ROBBERY_TYPE, robbery)
        HeistHelper.set_tunable_by_hash(tunables.SLOT2_VEHICLE_TYPE, vehicle)
        HeistHelper.set_tunable_by_hash(tunables.SLOT2_KEEP, 1)
    elseif slot == 3 then
        HeistHelper.set_tunable_by_hash(tunables.SLOT3_ROBBERY_TYPE, robbery)
        HeistHelper.set_tunable_by_hash(tunables.SLOT3_VEHICLE_TYPE, vehicle)
        HeistHelper.set_tunable_by_hash(tunables.SLOT3_KEEP, 1)
    end

    show_notify(_T("Slot") .. " " .. slot .. " " .. _T("configured"), "Success")
    return true
end

local function salvage_kill_cooldown()
    if not HeistHelper or not heist_data then return false end

    local tunables = heist_data.TUNABLES.SALVAGE
    HeistHelper.set_tunable(tunables.COOLDOWN_ROBBERY, 0)
    HeistHelper.set_tunable(tunables.COOLDOWN_CFR, 0)
    show_notify(_T("Cooldown reset"), "Success")
    return true
end

local function salvage_reload_board()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    local reload_vals = heist_data.VALUES

    if HeistHelper.is_script_running(scripts.SALVAGE_PLANNING) then
        HeistHelper.set_local(scripts.SALVAGE_PLANNING, locals.SALVAGE, reload_vals.BOARD_RELOAD)
        show_notify(_T("Board redrawn"), "Success")
        return true
    end

    show_notify(_T("Not in Salvage Yard"), "Warning")
    return false
end

local function salvage_force_start()
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.PLANNING
    local reload_vals = heist_data.VALUES

    if HeistHelper.is_script_running(scripts.SALVAGE_PLANNING) then
        HeistHelper.set_local(scripts.SALVAGE_PLANNING, locals.SALVAGE_FORCE, reload_vals.BOARD_RELOAD)
        show_notify(_T("Force started"), "Success")
        return true
    end

    show_notify(_T("Not in Salvage Yard"), "Warning")
    return false
end

local function salvage_complete_preps()
    if not HeistHelper or not heist_data then return false end

    local stats = heist_data.STATS.SALVAGE
    HeistHelper.set_stat(stats.GEN_BS, -1)
    HeistHelper.set_stat(stats.SCOPE_BS, -1)
    HeistHelper.set_stat(stats.FM_PROG, -1)
    HeistHelper.set_stat(stats.INST_PROG, -1)
    show_notify(_T("Preps completed"), "Success")
    return true
end

local function salvage_instant_finish(mission_script)
    if not HeistHelper or not heist_data then return false end

    local scripts = heist_data.SCRIPTS
    local locals = heist_data.LOCALS.SALVAGE

    -- Detect which mission is running
    local running_script = nil
    local step1, step2 = nil, nil

    if mission_script then
        running_script = mission_script
    elseif HeistHelper.is_script_running(scripts.VEHROB_CARGO) then
        running_script = scripts.VEHROB_CARGO
        step1 = locals.CARGO_STEP1
        step2 = locals.CARGO_STEP2
    elseif HeistHelper.is_script_running(scripts.VEHROB_POLICE) then
        running_script = scripts.VEHROB_POLICE
        step1 = locals.POLICE_STEP1
        step2 = locals.POLICE_STEP2
    elseif HeistHelper.is_script_running(scripts.VEHROB_ARENA) then
        running_script = scripts.VEHROB_ARENA
        step1 = locals.ARENA_STEP1
        step2 = locals.ARENA_STEP2
    elseif HeistHelper.is_script_running(scripts.VEHROB_CASINO) then
        running_script = scripts.VEHROB_CASINO
        step1 = locals.CASINO_STEP1
        step2 = locals.CASINO_STEP2
    elseif HeistHelper.is_script_running(scripts.VEHROB_SUBMARINE) then
        running_script = scripts.VEHROB_SUBMARINE
        step1 = locals.SUBMARINE_STEP1
        step2 = locals.SUBMARINE_STEP2
    end

    if not running_script then
        show_notify(_T("Not in Salvage Yard mission"), "Warning")
        return false
    end

    if step1 and step2 then
        HeistHelper.set_local(running_script, step1, 4)
        HeistHelper.set_local(running_script, step2, 5)
        show_notify(_T("Finishing mission..."), "Success")
        return true
    end

    show_notify(_T("Failed to finish"), "Error")
    return false
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Generic Tab]
-- -------------------------------------------------------------------------

local function draw_generic_tab()
    ui.DualColumn(
        function()
            -- Generic Controls
            ui.Group(_T("Generic Controls"), function()
                ui.Row({
                    function()
                        if ui.Button(_T("Skip Cutscene"), _T("Stop current cutscene")) then
                            generic_skip_cutscene()
                        end
                    end,
                    function()
                        if ui.Button(_T("Skip Checkpoint"), _T("Skip to next checkpoint")) then
                            generic_skip_checkpoint()
                        end
                    end
                })
            end)
        end,
        function()
            -- Generic Cuts
            ui.Group(_T("Generic Cuts"), function()
                state.generic.cut_value = ui.Slider(_T("Cut Percentage"), state.generic.cut_value, 0, 999, 1)

                ui.Row({
                    function()
                        if ui.Button(_T("100%")) then
                            state.generic.cut_value = 100
                        end
                    end,
                    function()
                        if ui.Button(_T("Apply"), _T("Apply generic cut value (0-999%)")) then
                            generic_apply_cut(state.generic.cut_value)
                        end
                    end
                })
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Apartment Tab]
-- -------------------------------------------------------------------------

local function draw_apartment_tab()
    ui.DualColumn(
        function()
            -- Launch Section
            ui.Group(_T("Launch"), function()
                state.solo_launch.apartment = ui.Checkbox(_T("Solo Launch"), state.solo_launch.apartment)

                ui.Row({
                    function()
                        if ui.Button(_T("Force Ready"), _T("Force all players ready")) then
                            apartment_force_ready()
                        end
                    end,
                    function()
                        if ui.Button(_T("Redraw Board")) then
                            apartment_redraw_board()
                        end
                    end
                })
            end)

            -- Preps Section
            ui.Group(_T("Preps"), function()
                ui.Row({
                    function()
                        if ui.Button(_T("Complete Preps")) then
                            apartment_complete_preps()
                        end
                    end,
                    function()
                        if ui.Button(_T("Kill Cooldown")) then
                            apartment_kill_cooldown()
                        end
                    end
                })
            end)

            -- 12M Bonus Section
            ui.Group(_T("Bonuses"), function()
                ui.Row({
                    function()
                        if ui.Button(_T("Enable 12M Bonus"), _T("Enable $12M all-in-order bonus")) then
                            apartment_12mil_bonus(true)
                        end
                    end,
                    function()
                        if ui.Button(_T("Disable 12M Bonus")) then
                            apartment_12mil_bonus(false)
                        end
                    end
                })
            end)

            -- Cuts Section
            ui.Collapsible(_T("Cuts"), function()
                state.apartment.player1_cut = ui.Slider(_T("Player 1"), state.apartment.player1_cut, 0, 999, 1)
                state.apartment.player2_cut = ui.Slider(_T("Player 2"), state.apartment.player2_cut, 0, 999, 1)
                state.apartment.player3_cut = ui.Slider(_T("Player 3"), state.apartment.player3_cut, 0, 999, 1)
                state.apartment.player4_cut = ui.Slider(_T("Player 4"), state.apartment.player4_cut, 0, 999, 1)

                ui.Row({
                    function()
                        state.apartment.preset_cut = ui.TextInput(_T("Preset") .. "##apartment_preset", state.apartment.preset_cut, 4)
                    end,
                    function()
                        if ui.Button(_T("Apply Preset") .. "##apartment_preset_apply") then
                            local val = tonumber(state.apartment.preset_cut) or 100
                            state.apartment.player1_cut = val
                            state.apartment.player2_cut = val
                            state.apartment.player3_cut = val
                            state.apartment.player4_cut = val
                        end
                    end
                })

                if ui.Button(_T("Apply Cuts"), _T("Apply cuts to game")) then
                    apply_apartment_cuts({
                        state.apartment.player1_cut,
                        state.apartment.player2_cut,
                        state.apartment.player3_cut,
                        state.apartment.player4_cut
                    })
                end
            end)
        end,
        function()
            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Apartment entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.APARTMENT, "Apartment")
                end

                if ui.Button(_T("Teleport to Heist Board"), _T("Teleport to heist board (enter first)")) then
                    teleport_to_heist_blip(173.376, "Apartment Heist Board")
                end

                ui.Separator()

                -- Hack bypass buttons
                ui.Row({
                    function()
                        if ui.Button(_T("Fleeca Hack")) then
                            assist_skip_fleeca_hack()
                        end
                    end,
                    function()
                        if ui.Button(_T("Fleeca Drill")) then
                            assist_skip_fleeca_drill()
                        end
                    end
                })

                if ui.Button(_T("Pacific Hack")) then
                    assist_skip_pacific_hack()
                end

                ui.Separator()

                if ui.Button(_T("Instant Finish"), _T("Force complete Apartment heist finale")) then
                    apartment_instant_finish()
                end

                ui.Separator()

                ui.Row({
                    function()
                        if ui.Button(_T("Play Unavailable"), _T("Make unavailable jobs playable temporarily")) then
                            apartment_play_unavailable()
                        end
                    end,
                    function()
                        if ui.Button(_T("Unlock All Jobs"), _T("Unlock all heists - restart game to apply")) then
                            apartment_unlock_all_jobs()
                        end
                    end
                })
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Agency Tab]
-- -------------------------------------------------------------------------

local function draw_agency_tab()
    local options = heist_data.OPTIONS.AGENCY_CONTRACTS
    local names, _ = heist_data.build_options(options)
    names = translate_options(names)

    ui.DualColumn(
        function()
            -- Preps Section
            ui.Group(_T("Preps"), function()
                state.agency.contract_idx = ui.Combo(_T("Contract"), state.agency.contract_idx, names)

                ui.Row({
                    function()
                        if ui.Button(_T("Complete Preps")) then
                            local contract_value = heist_data.get_option_value(options, state.agency.contract_idx)
                            agency_complete_preps(contract_value)
                        end
                    end,
                    function()
                        if ui.Button(_T("Reset Cooldown")) then
                            agency_reset_cooldown()
                        end
                    end
                })
            end)

            -- Payout Section
            ui.Group(_T("Payout"), function()
                state.agency.payout = ui.Slider(_T("Payout Amount"), state.agency.payout, 0, 2500000, 50000)

                ui.Row({
                    function()
                        if ui.Button(_T("Max")) then
                            state.agency.payout = 2500000
                        end
                    end,
                    function()
                        if ui.Button(_T("Apply")) then
                            agency_set_payout(state.agency.payout)
                        end
                    end
                })
            end)
        end,
        function()
            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Agency entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.AGENCY, "Agency")
                end

                if ui.Button(_T("Teleport to Computer"), _T("Teleport to Agency computer (enter first)")) then
                    teleport_to_interior(TELEPORT_COORDS.AGENCY, "Agency Computer")
                end

                if ui.Button(_T("Instant Finish"), _T("Force complete Agency contract finale")) then
                    assist_instant_finish_agency()
                end
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Cayo Perico Tab]
-- -------------------------------------------------------------------------

local function draw_cayo_tab()
    local difficulty_opts = heist_data.OPTIONS.CAYO_DIFFICULTY
    local approach_opts = heist_data.OPTIONS.CAYO_APPROACH
    local loadout_opts = heist_data.OPTIONS.CAYO_LOADOUT
    local target_opts = heist_data.OPTIONS.CAYO_TARGET
    local secondary_opts = heist_data.OPTIONS.CAYO_SECONDARY
    local compound_amount_opts = heist_data.OPTIONS.CAYO_COMPOUND_AMOUNT
    local island_amount_opts = heist_data.OPTIONS.CAYO_ISLAND_AMOUNT

    ui.DualColumn(
        function()
            -- Preps Section
            ui.Group(_T("Preps"), function()
                local diff_names, _ = heist_data.build_options(difficulty_opts)
                diff_names = translate_options(diff_names)
                state.cayo.difficulty_idx = ui.Combo(_T("Difficulty"), state.cayo.difficulty_idx, diff_names)

                local approach_names, _ = heist_data.build_options(approach_opts)
                approach_names = translate_options(approach_names)
                state.cayo.approach_idx = ui.Combo(_T("Approach"), state.cayo.approach_idx, approach_names)

                local loadout_names, _ = heist_data.build_options(loadout_opts)
                loadout_names = translate_options(loadout_names)
                state.cayo.loadout_idx = ui.Combo(_T("Loadout"), state.cayo.loadout_idx, loadout_names)

                local target_names, _ = heist_data.build_options(target_opts)
                target_names = translate_options(target_names)
                state.cayo.target_idx = ui.Combo(_T("Primary Target"), state.cayo.target_idx, target_names)

                ui.Separator()

                local secondary_names, _ = heist_data.build_options(secondary_opts)
                secondary_names = translate_options(secondary_names)
                state.cayo.compound_loot_idx = ui.Combo(_T("Compound Loot"), state.cayo.compound_loot_idx, secondary_names)

                local compound_amt_names, _ = heist_data.build_options(compound_amount_opts)
                compound_amt_names = translate_options(compound_amt_names)
                state.cayo.compound_amount_idx = ui.Combo(_T("Compound Amount"), state.cayo.compound_amount_idx, compound_amt_names)

                state.cayo.island_loot_idx = ui.Combo(_T("Island Loot"), state.cayo.island_loot_idx, secondary_names)

                local island_amt_names, _ = heist_data.build_options(island_amount_opts)
                island_amt_names = translate_options(island_amt_names)
                state.cayo.island_amount_idx = ui.Combo(_T("Island Amount"), state.cayo.island_amount_idx, island_amt_names)

                ui.Separator()

                if ui.Button(_T("Apply & Complete Preps")) then
                    local difficulty = heist_data.get_option_value(difficulty_opts, state.cayo.difficulty_idx)
                    local approach = heist_data.get_option_value(approach_opts, state.cayo.approach_idx)
                    local loadout = heist_data.get_option_value(loadout_opts, state.cayo.loadout_idx)
                    local target = heist_data.get_option_value(target_opts, state.cayo.target_idx)
                    local compound_loot = heist_data.get_option_value(secondary_opts, state.cayo.compound_loot_idx)
                    local compound_amount = heist_data.get_option_value(compound_amount_opts, state.cayo.compound_amount_idx)
                    local island_loot = heist_data.get_option_value(secondary_opts, state.cayo.island_loot_idx)
                    local island_amount = heist_data.get_option_value(island_amount_opts, state.cayo.island_amount_idx)

                    cayo_complete_preps(difficulty, approach, loadout, target, compound_loot, compound_amount, island_loot, island_amount)
                end

                ui.Row({
                    function()
                        if ui.Button(_T("Reload Board")) then
                            cayo_reload_board()
                        end
                    end,
                    function()
                        if ui.Button(_T("Reset Cooldown")) then
                            cayo_reset_cooldown()
                        end
                    end
                })

                ui.Row({
                    function()
                        if ui.Button(_T("Reset Preps")) then
                            cayo_reset_preps()
                        end
                    end,
                    function()
                        if ui.Button(_T("Force Ready"), _T("Force all players ready")) then
                            cayo_force_ready()
                        end
                    end
                })
            end)
        end,
        function()
            -- Target Values Section
            ui.Group(_T("Target Values"), function()
                state.cayo.cash_value = ui.Slider(_T("Cash"), state.cayo.cash_value, 0, 500000, 5000)
                state.cayo.weed_value = ui.Slider(_T("Weed"), state.cayo.weed_value, 0, 500000, 5000)
                state.cayo.coke_value = ui.Slider(_T("Coke"), state.cayo.coke_value, 0, 500000, 5000)
                state.cayo.gold_value = ui.Slider(_T("Gold"), state.cayo.gold_value, 0, 500000, 5000)

                ui.Row({
                    function()
                        if ui.Button(_T("Default")) then
                            local defaults = heist_data.DEFAULTS.CAYO
                            state.cayo.cash_value = defaults.CASH_VALUE
                            state.cayo.weed_value = defaults.WEED_VALUE
                            state.cayo.coke_value = defaults.COKE_VALUE
                            state.cayo.gold_value = defaults.GOLD_VALUE
                        end
                    end,
                    function()
                        if ui.Button(_T("Apply")) then
                            local stats = heist_data.STATS.CAYO
                            HeistHelper.set_stat(stats.LOOT_CASH_V, state.cayo.cash_value)
                            HeistHelper.set_stat(stats.LOOT_WEED_V, state.cayo.weed_value)
                            HeistHelper.set_stat(stats.LOOT_COKE_V, state.cayo.coke_value)
                            HeistHelper.set_stat(stats.LOOT_GOLD_V, state.cayo.gold_value)
                            show_notify(_T("Loot values applied"), "Success")
                        end
                    end
                })
            end)

            -- Cuts Section
            ui.Collapsible(_T("Cuts"), function()
                state.cayo.player1_cut = ui.Slider(_T("Player 1"), state.cayo.player1_cut, 0, 999, 1)
                state.cayo.player2_cut = ui.Slider(_T("Player 2"), state.cayo.player2_cut, 0, 999, 1)
                state.cayo.player3_cut = ui.Slider(_T("Player 3"), state.cayo.player3_cut, 0, 999, 1)
                state.cayo.player4_cut = ui.Slider(_T("Player 4"), state.cayo.player4_cut, 0, 999, 1)

                ui.Row({
                    function()
                        state.cayo.preset_cut = ui.TextInput(_T("Preset") .. "##cayo_preset", state.cayo.preset_cut, 4)
                    end,
                    function()
                        if ui.Button(_T("Apply Preset") .. "##cayo_preset_apply") then
                            local val = tonumber(state.cayo.preset_cut) or 100
                            state.cayo.player1_cut = val
                            state.cayo.player2_cut = val
                            state.cayo.player3_cut = val
                            state.cayo.player4_cut = val
                        end
                    end
                })

                if ui.Button(_T("Apply Cuts"), _T("Apply cuts to game")) then
                    apply_cayo_cuts({
                        state.cayo.player1_cut,
                        state.cayo.player2_cut,
                        state.cayo.player3_cut,
                        state.cayo.player4_cut
                    })
                end
            end)

            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Kosatka entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.KOSATKA, "Kosatka")
                end

                if ui.Button(_T("Teleport to Planning Screen"), _T("Teleport to planning screen (enter first)")) then
                    teleport_to_interior(TELEPORT_COORDS.KOSATKA, "Kosatka Planning Screen")
                end

                ui.Separator()

                -- Hack bypass buttons
                ui.Row({
                    function()
                        if ui.Button(_T("Fingerprint Hack")) then
                            assist_skip_fingerprint()
                        end
                    end,
                    function()
                        if ui.Button(_T("Plasma Cutter")) then
                            assist_skip_plasma_cutter()
                        end
                    end
                })

                if ui.Button(_T("Drainage Pipe Cut")) then
                    assist_skip_drainage_pipe()
                end

                ui.Separator()

                ui.Row({
                    function()
                        if ui.Button(_T("Infinite Bag ON")) then
                            cayo_infinite_bag(true)
                        end
                    end,
                    function()
                        if ui.Button(_T("Infinite Bag OFF")) then
                            cayo_infinite_bag(false)
                        end
                    end
                })

                ui.Row({
                    function()
                        if ui.Button(_T("Remove Crew Cuts"), _T("Remove Pavel's cut")) then
                            cayo_remove_crew_cuts(true)
                        end
                    end,
                    function()
                        if ui.Button(_T("Restore Crew Cuts")) then
                            cayo_remove_crew_cuts(false)
                        end
                    end
                })

                ui.Separator()

                if ui.Button(_T("Instant Finish"), _T("Force complete Cayo Perico heist finale")) then
                    assist_instant_finish_cayo()
                end
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Diamond Casino Tab]
-- -------------------------------------------------------------------------

local function draw_casino_tab()
    ui.DualColumn(
        function()
            -- Preps Section
            ui.Group(_T("Preps"), function()
                local difficulty_names = { _T("Normal"), _T("Hard") }
                state.casino.difficulty_idx = ui.Combo(_T("Difficulty"), state.casino.difficulty_idx, difficulty_names)

                local approach_names = { _T("Silent & Sneaky"), _T("The Big Con"), _T("Aggressive") }
                state.casino.approach_idx = ui.Combo(_T("Approach"), state.casino.approach_idx, approach_names)

                local target_names = { _T("Cash"), _T("Artwork"), _T("Gold"), _T("Diamonds") }
                state.casino.target_idx = ui.Combo(_T("Target"), state.casino.target_idx, target_names)

                ui.Separator()

                local gunman_names = { _T("Karl Abolaji"), _T("Gustavo Mota"), _T("Charlie Reed"), _T("Chester McCoy"), _T("Patrick McReary") }
                state.casino.gunman_idx = ui.Combo(_T("Gunman"), state.casino.gunman_idx, gunman_names)

                local driver_names = { _T("Karim Denz"), _T("Taliana Martinez"), _T("Eddie Toh"), _T("Zach Nelson"), _T("Chester McCoy") }
                state.casino.driver_idx = ui.Combo(_T("Driver"), state.casino.driver_idx, driver_names)

                local hacker_names = { _T("Rickie Lukens"), _T("Christian Feltz"), _T("Yohan Blair"), _T("Avi Schwartzman"), _T("Paige Harris") }
                state.casino.hacker_idx = ui.Combo(_T("Hacker"), state.casino.hacker_idx, hacker_names)

                local masks_names = { _T("None"), _T("Geometric"), _T("Hunter"), _T("Oni Half"), _T("Emoji"), _T("Ornate Skull"), _T("Lucky Pig"), _T("Crackshot"), _T("Gurilla"), _T("Clown"), _T("Animal"), _T("Penalty"), _T("Christmas") }
                state.casino.masks_idx = ui.Combo(_T("Masks"), state.casino.masks_idx, masks_names)

                local keycards_names = { _T("None"), _T("Level 1"), _T("Level 2") }
                state.casino.keycards_idx = ui.Combo(_T("Keycards"), state.casino.keycards_idx, keycards_names)

                ui.Separator()

                if ui.Button(_T("Apply & Complete Preps")) then
                    casino_complete_preps(
                        state.casino.difficulty_idx - 1,
                        state.casino.approach_idx,
                        state.casino.gunman_idx,
                        state.casino.driver_idx,
                        state.casino.hacker_idx,
                        state.casino.masks_idx - 1,
                        state.casino.keycards_idx - 1,
                        3, -- Guards disruption level
                        state.casino.target_idx - 1
                    )
                end

                ui.Row({
                    function()
                        if ui.Button(_T("Reset Preps")) then
                            casino_reset_preps()
                        end
                    end,
                    function()
                        if ui.Button(_T("Redraw Boards")) then
                            casino_reload_board()
                        end
                    end
                })

                ui.Row({
                    function()
                        if ui.Button(_T("Reset Cooldown")) then
                            casino_reset_cooldown()
                        end
                    end,
                    function()
                        -- Placeholder
                    end
                })
            end)
        end,
        function()
            -- Launch Section
            ui.Group(_T("Launch"), function()
                state.solo_launch.casino = ui.Checkbox(_T("Solo Launch"), state.solo_launch.casino)

                ui.Row({
                    function()
                        if ui.Button(_T("Force Ready"), _T("Force all players ready")) then
                            casino_force_ready()
                        end
                    end,
                    function()
                        if ui.Button(_T("Skip Setup"), _T("Skip Arcade setup mission")) then
                            casino_skip_setup()
                        end
                    end
                })

                ui.Row({
                    function()
                        if ui.Button(_T("Infinite Bag ON")) then
                            casino_infinite_bag(true)
                        end
                    end,
                    function()
                        if ui.Button(_T("Infinite Bag OFF")) then
                            casino_infinite_bag(false)
                        end
                    end
                })

                ui.Row({
                    function()
                        if ui.Button(_T("Remove Crew Cuts"), _T("Remove gunman/driver/hacker cuts")) then
                            casino_remove_crew_cuts(true)
                        end
                    end,
                    function()
                        if ui.Button(_T("Restore Crew Cuts")) then
                            casino_remove_crew_cuts(false)
                        end
                    end
                })
            end)

            -- Cuts Section
            ui.Collapsible(_T("Cuts"), function()
                state.casino.player1_cut = ui.Slider(_T("Player 1"), state.casino.player1_cut, 0, 999, 1)
                state.casino.player2_cut = ui.Slider(_T("Player 2"), state.casino.player2_cut, 0, 999, 1)
                state.casino.player3_cut = ui.Slider(_T("Player 3"), state.casino.player3_cut, 0, 999, 1)
                state.casino.player4_cut = ui.Slider(_T("Player 4"), state.casino.player4_cut, 0, 999, 1)

                ui.Row({
                    function()
                        state.casino.preset_cut = ui.TextInput(_T("Preset") .. "##casino_preset", state.casino.preset_cut, 4)
                    end,
                    function()
                        if ui.Button(_T("Apply Preset") .. "##casino_preset_apply") then
                            local val = tonumber(state.casino.preset_cut) or 100
                            state.casino.player1_cut = val
                            state.casino.player2_cut = val
                            state.casino.player3_cut = val
                            state.casino.player4_cut = val
                        end
                    end
                })

                if ui.Button(_T("Apply Cuts"), _T("Apply cuts to game")) then
                    apply_casino_cuts({
                        state.casino.player1_cut,
                        state.casino.player2_cut,
                        state.casino.player3_cut,
                        state.casino.player4_cut
                    })
                end
            end)

            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Arcade entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.ARCADE, "Arcade")
                end

                if ui.Button(_T("Teleport to Planning Board"), _T("Teleport to planning board (enter first)")) then
                    teleport_to_interior(TELEPORT_COORDS.ARCADE, "Arcade Planning Board")
                end

                ui.Separator()

                -- Hack bypass buttons
                ui.Row({
                    function()
                        if ui.Button(_T("Fingerprint Hack")) then
                            assist_skip_fingerprint()
                        end
                    end,
                    function()
                        if ui.Button(_T("Keypad Hack")) then
                            assist_skip_keypad()
                        end
                    end
                })

                if ui.Button(_T("Vault Door Drill")) then
                    assist_skip_vault_drill()
                end

                ui.Separator()

                if ui.Button(_T("Instant Finish"), _T("Force complete Casino heist finale")) then
                    assist_instant_finish_casino()
                end
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Auto Shop Tab]
-- -------------------------------------------------------------------------

local function draw_autoshop_tab()
    local options = heist_data.OPTIONS.AUTOSHOP_CONTRACTS
    local names, _ = heist_data.build_options(options)
    names = translate_options(names)

    ui.DualColumn(
        function()
            -- Preps Section
            ui.Group(_T("Preps"), function()
                state.autoshop.contract_idx = ui.Combo(_T("Contract"), state.autoshop.contract_idx, names)

                if ui.Button(_T("Apply & Complete Preps")) then
                    local contract_value = heist_data.get_option_value(options, state.autoshop.contract_idx)
                    autoshop_complete_preps(contract_value)
                end

                ui.Row({
                    function()
                        if ui.Button(_T("Reset Preps")) then
                            autoshop_reset_preps()
                        end
                    end,
                    function()
                        if ui.Button(_T("Redraw Board")) then
                            autoshop_redraw_board()
                        end
                    end
                })

                if ui.Button(_T("Kill Cooldown"), _T("Reset contract cooldown")) then
                    autoshop_kill_cooldown()
                end
            end)

            -- Payout Section
            ui.Group(_T("Payout"), function()
                state.autoshop.payout = ui.Slider(_T("Payout Amount"), state.autoshop.payout, 0, 2200000, 50000)

                ui.Row({
                    function()
                        if ui.Button(_T("Max")) then
                            state.autoshop.payout = 2200000
                        end
                    end,
                    function()
                        if ui.Button(_T("Apply")) then
                            autoshop_set_payout(state.autoshop.payout)
                        end
                    end
                })
            end)
        end,
        function()
            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Auto Shop entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.AUTOSHOP, "Auto Shop")
                end

                if ui.Button(_T("Teleport to Contract Board"), _T("Teleport to contract board (enter first)")) then
                    teleport_to_interior(TELEPORT_COORDS.AUTOSHOP, "Auto Shop Contract Board")
                end

                if ui.Button(_T("Instant Finish"), _T("Force complete Auto Shop contract finale")) then
                    assist_instant_finish_autoshop()
                end
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Doomsday Tab]
-- -------------------------------------------------------------------------

local function draw_doomsday_tab()
    local act_opts = heist_data.OPTIONS.DOOMSDAY_ACTS
    local act_names, _ = heist_data.build_options(act_opts)
                act_names = translate_options(act_names)

    ui.DualColumn(
        function()
            -- Preps Section
            ui.Group(_T("Preps"), function()
                state.doomsday.act_idx = ui.Combo(_T("Act"), state.doomsday.act_idx, act_names)

                if ui.Button(_T("Apply & Complete Preps")) then
                    local act = heist_data.get_option_value(act_opts, state.doomsday.act_idx)
                    local presets = heist_data.OPTIONS.DOOMSDAY_PRESETS
                    local preset = presets["ACT" .. act]

                    if preset and HeistHelper then
                        local stats = heist_data.STATS.DOOMSDAY
                        HeistHelper.set_stat(stats.FLOW_MISSION_PROG, preset.flow)
                        HeistHelper.set_stat(stats.HEIST_STATUS, preset.status)
                        HeistHelper.set_stat(stats.FLOW_NOTIFICATIONS, preset.notifications)
                        show_notify(_T("Doomsday preps completed"), "Success")
                    end
                end

                if ui.Button(_T("Reset Progress")) then
                    local presets = heist_data.OPTIONS.DOOMSDAY_PRESETS
                    local reset = presets.RESET

                    if reset and HeistHelper then
                        local stats = heist_data.STATS.DOOMSDAY
                        HeistHelper.set_stat(stats.FLOW_MISSION_PROG, reset.flow)
                        HeistHelper.set_stat(stats.HEIST_STATUS, reset.status)
                        HeistHelper.set_stat(stats.FLOW_NOTIFICATIONS, reset.notifications)
                        show_notify(_T("Doomsday progress reset"), "Success")
                    end
                end

                ui.Row({
                    function()
                        if ui.Button(_T("Reset Preps")) then
                            doomsday_reset_preps()
                        end
                    end,
                    function()
                        if ui.Button(_T("Redraw Board")) then
                            doomsday_reload_board()
                        end
                    end
                })
            end)

            -- Launch Section
            ui.Group(_T("Launch"), function()
                state.solo_launch.doomsday = ui.Checkbox(_T("Solo Launch"), state.solo_launch.doomsday)

                if ui.Button(_T("Force Ready"), _T("Force all players ready")) then
                    doomsday_force_ready()
                end
            end)
        end,
        function()
            -- Cuts Section
            ui.Group(_T("Cuts"), function()
                state.doomsday.player1_cut = ui.Slider(_T("Player 1"), state.doomsday.player1_cut, 0, 999, 1)
                state.doomsday.player2_cut = ui.Slider(_T("Player 2"), state.doomsday.player2_cut, 0, 999, 1)
                state.doomsday.player3_cut = ui.Slider(_T("Player 3"), state.doomsday.player3_cut, 0, 999, 1)
                state.doomsday.player4_cut = ui.Slider(_T("Player 4"), state.doomsday.player4_cut, 0, 999, 1)

                ui.Row({
                    function()
                        state.doomsday.preset_cut = ui.TextInput(_T("Preset") .. "##doomsday_preset", state.doomsday.preset_cut, 4)
                    end,
                    function()
                        if ui.Button(_T("Apply Preset") .. "##doomsday_preset_apply") then
                            local val = tonumber(state.doomsday.preset_cut) or 100
                            state.doomsday.player1_cut = val
                            state.doomsday.player2_cut = val
                            state.doomsday.player3_cut = val
                            state.doomsday.player4_cut = val
                        end
                    end
                })

                if ui.Button(_T("Apply Cuts"), _T("Apply cuts to game")) then
                    apply_doomsday_cuts({
                        state.doomsday.player1_cut,
                        state.doomsday.player2_cut,
                        state.doomsday.player3_cut,
                        state.doomsday.player4_cut
                    })
                end
            end)

            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Facility entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.FACILITY, "Facility")
                end

                if ui.Button(_T("Teleport to Screen"), _T("Teleport to Doomsday screen (enter first)")) then
                    -- Doomsday uses Heist blip (428) + heading 325.726 for interior
                    teleport_to_heist_blip(325.726, "Doomsday Screen")
                end

                ui.Separator()

                -- Hack bypass buttons
                ui.Row({
                    function()
                        if ui.Button(_T("Data Hack")) then
                            assist_skip_data_hack()
                        end
                    end,
                    function()
                        if ui.Button(_T("Doomsday Hack")) then
                            assist_skip_doomsday_hack()
                        end
                    end
                })

                ui.Separator()

                if ui.Button(_T("Instant Finish"), _T("Force complete Doomsday heist finale")) then
                    assist_instant_finish_doomsday()
                end
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Drawing Functions - Salvage Yard Tab]
-- -------------------------------------------------------------------------

local function draw_salvage_tab()
    local robbery_opts = heist_data.OPTIONS.SALVAGE_ROBBERIES
    local vehicle_opts = heist_data.OPTIONS.SALVAGE_VEHICLES

    local robbery_names, _ = heist_data.build_options(robbery_opts)
                robbery_names = translate_options(robbery_names)
    local vehicle_names, _ = heist_data.build_options(vehicle_opts)
                vehicle_names = translate_options(vehicle_names)

    ui.DualColumn(
        function()
            -- Slot 1
            ui.Group(_T("Slot 1"), function()
                state.salvage.slot1_robbery_idx = ui.Combo(_T("Robbery") .. "##slot1_rob", state.salvage.slot1_robbery_idx, robbery_names)
                state.salvage.slot1_vehicle_idx = ui.Combo(_T("Vehicle") .. "##slot1_veh", state.salvage.slot1_vehicle_idx, vehicle_names)

                if ui.Button(_T("Apply Slot 1") .. "##apply1") then
                    salvage_set_slot(1, state.salvage.slot1_robbery_idx, state.salvage.slot1_vehicle_idx)
                end
            end)

            -- Slot 2
            ui.Group(_T("Slot 2"), function()
                state.salvage.slot2_robbery_idx = ui.Combo(_T("Robbery") .. "##slot2_rob", state.salvage.slot2_robbery_idx, robbery_names)
                state.salvage.slot2_vehicle_idx = ui.Combo(_T("Vehicle") .. "##slot2_veh", state.salvage.slot2_vehicle_idx, vehicle_names)

                if ui.Button(_T("Apply Slot 2") .. "##apply2") then
                    salvage_set_slot(2, state.salvage.slot2_robbery_idx, state.salvage.slot2_vehicle_idx)
                end
            end)

            -- Slot 3
            ui.Group(_T("Slot 3"), function()
                state.salvage.slot3_robbery_idx = ui.Combo(_T("Robbery") .. "##slot3_rob", state.salvage.slot3_robbery_idx, robbery_names)
                state.salvage.slot3_vehicle_idx = ui.Combo(_T("Vehicle") .. "##slot3_veh", state.salvage.slot3_vehicle_idx, vehicle_names)

                if ui.Button(_T("Apply Slot 3") .. "##apply3") then
                    salvage_set_slot(3, state.salvage.slot3_robbery_idx, state.salvage.slot3_vehicle_idx)
                end
            end)
        end,
        function()
            -- Preps Section
            ui.Group(_T("Preps"), function()
                ui.Row({
                    function()
                        if ui.Button(_T("Complete Preps")) then
                            salvage_complete_preps()
                        end
                    end,
                    function()
                        if ui.Button(_T("Kill Cooldown")) then
                            salvage_kill_cooldown()
                        end
                    end
                })

                ui.Row({
                    function()
                        if ui.Button(_T("Reload Board")) then
                            salvage_reload_board()
                        end
                    end,
                    function()
                        if ui.Button(_T("Force Start")) then
                            salvage_force_start()
                        end
                    end
                })
            end)

            -- Misc Section
            ui.Group(_T("Misc"), function()
                -- Teleport buttons
                if ui.Button(_T("Teleport to Entrance"), _T("Teleport to Salvage Yard entrance")) then
                    teleport_to_entrance(BLIP_SPRITES.SALVAGE, "Salvage Yard")
                end

                if ui.Button(_T("Teleport Inside"), _T("Teleport inside Salvage Yard (enter first)")) then
                    teleport_to_interior(TELEPORT_COORDS.SALVAGE, "Salvage Yard")
                end

                ui.Separator()

                if ui.Button(_T("Instant Finish"), _T("Force complete current Salvage Yard mission")) then
                    salvage_instant_finish()
                end
            end)
        end
    )
end

-- -------------------------------------------------------------------------
-- [Config Save/Load]
-- -------------------------------------------------------------------------

local function load_config()
    if not features_config then return end
    local saved = features_config.get_module(MODULE_NAME)
    if not saved then return end

    -- Load saved state
    if saved.subtab_idx then state.subtab_idx = saved.subtab_idx end
    if saved.solo_launch then
        for k, v in pairs(saved.solo_launch) do state.solo_launch[k] = v end
    end
    if saved.agency then
        for k, v in pairs(saved.agency) do state.agency[k] = v end
    end
    if saved.cayo then
        for k, v in pairs(saved.cayo) do state.cayo[k] = v end
    end
    if saved.casino then
        for k, v in pairs(saved.casino) do state.casino[k] = v end
    end
    if saved.autoshop then
        for k, v in pairs(saved.autoshop) do state.autoshop[k] = v end
    end
    if saved.doomsday then
        for k, v in pairs(saved.doomsday) do state.doomsday[k] = v end
    end
    if saved.apartment then
        for k, v in pairs(saved.apartment) do state.apartment[k] = v end
    end
    if saved.salvage then
        for k, v in pairs(saved.salvage) do state.salvage[k] = v end
    end
    if saved.generic then
        for k, v in pairs(saved.generic) do state.generic[k] = v end
    end
end

local function save_config()
    if not features_config then return end
    features_config.set_module(MODULE_NAME, {
        subtab_idx = state.subtab_idx,
        solo_launch = state.solo_launch,
        agency = state.agency,
        cayo = state.cayo,
        casino = state.casino,
        autoshop = state.autoshop,
        doomsday = state.doomsday,
        apartment = state.apartment,
        salvage = state.salvage,
        generic = state.generic,
    }, true)
end

-- -------------------------------------------------------------------------
-- [Module Interface]
-- -------------------------------------------------------------------------

function module.init(u, _, nat, _, _, _, debug_lib)
    ui = u
    natives = nat
    debugger = debug_lib

    -- Cache natives
    if natives then
        N.GET_FIRST_BLIP_INFO_ID = natives.GET_FIRST_BLIP_INFO_ID
        N.DOES_BLIP_EXIST = natives.DOES_BLIP_EXIST
        N.GET_BLIP_COLOUR = natives.GET_BLIP_COLOUR
        N.GET_BLIP_COORDS = natives.GET_BLIP_COORDS
        N.GET_NEXT_BLIP_INFO_ID = natives.GET_NEXT_BLIP_INFO_ID
        N.FREEZE_ENTITY_POSITION = natives.FREEZE_ENTITY_POSITION
        N.SET_ENTITY_COORDS_NO_OFFSET = natives.SET_ENTITY_COORDS_NO_OFFSET
        N.SET_ENTITY_HEADING = natives.SET_ENTITY_HEADING
        N.STOP_CUTSCENE_IMMEDIATELY = natives.STOP_CUTSCENE_IMMEDIATELY
    end

    -- Load dependencies
    local ok, notif = pcall(require, "ClickUl.UI.notification")
    if ok then notification = notif end

    local ok2, fc = pcall(require, "ClickUl.Config.features")
    if ok2 and fc then features_config = fc end

    -- Load heist data and helper
    local ok3, hd = pcall(require, "ClickUl.Data.heist")
    if ok3 and hd then
        heist_data = hd
        log_info("Heist data loaded")
    else
        log_error("Failed to load heist data: " .. tostring(hd))
    end

    local ok4, hh = pcall(require, "ClickUl.Utils.HeistHelper")
    if ok4 and hh then
        HeistHelper = hh
        HeistHelper.init(debugger)
        log_info("HeistHelper initialized")
    else
        log_error("Failed to load HeistHelper: " .. tostring(hh))
    end

    -- Load saved config
    load_config()

    log_info("Heist module initialized")
end

function module.draw()
    local tabs = {
        _T("Generic"),
        _T("Agency"),
        _T("Apartment"),
        _T("Auto Shop"),
        _T("Cayo Perico"),
        _T("Diamond Casino"),
        _T("Doomsday"),
        _T("Salvage Yard"),
    }

    state.subtab_idx = ui.SubTabBar(tabs, state.subtab_idx or 1)

    -- Draw content based on selected tab
    if state.subtab_idx == 1 then
        draw_generic_tab()
    elseif state.subtab_idx == 2 then
        draw_agency_tab()
    elseif state.subtab_idx == 3 then
        draw_apartment_tab()
    elseif state.subtab_idx == 4 then
        draw_autoshop_tab()
    elseif state.subtab_idx == 5 then
        draw_cayo_tab()
    elseif state.subtab_idx == 6 then
        draw_casino_tab()
    elseif state.subtab_idx == 7 then
        draw_doomsday_tab()
    elseif state.subtab_idx == 8 then
        draw_salvage_tab()
    end
end

function module.on_menu_close()
    save_config()
end

function module.on_tick()
    -- Solo Launch: Apartment Heist
    if state.solo_launch.apartment then
        solo_launch_generic()
    elseif state.solo_launch_prev.apartment then
        -- Just turned off, reset to normal
        solo_launch_reset_apartment()
    end

    -- Solo Launch: Diamond Casino
    if state.solo_launch.casino then
        solo_launch_generic()
        solo_launch_casino_setup()
    elseif state.solo_launch_prev.casino then
        -- Just turned off, reset to normal
        solo_launch_reset_casino()
    end

    -- Solo Launch: Doomsday
    if state.solo_launch.doomsday then
        solo_launch_generic()
    elseif state.solo_launch_prev.doomsday then
        -- Just turned off, reset to normal
        solo_launch_reset_doomsday()
    end

    -- Update previous state
    state.solo_launch_prev.apartment = state.solo_launch.apartment
    state.solo_launch_prev.casino = state.solo_launch.casino
    state.solo_launch_prev.doomsday = state.solo_launch.doomsday
end

return module
